renderling/
gltf.rs

1//! GLTF support.
2//!
3//! # Loading GLTF files
4//!
5//! Loading GLTF files is accomplished through [`Stage::load_gltf_document_from_path`]
6//! and [`Stage::load_gltf_document_from_bytes`].
7use std::{collections::HashMap, sync::Arc};
8
9use craballoc::prelude::*;
10use crabslab::{Array, Id};
11use glam::{Mat4, Quat, Vec2, Vec3, Vec4};
12use rustc_hash::{FxHashMap, FxHashSet};
13use snafu::{OptionExt, ResultExt, Snafu};
14
15use crate::{
16    atlas::{
17        shader::AtlasTextureDescriptor, AtlasError, AtlasImage, AtlasTexture, TextureAddressMode,
18        TextureModes,
19    },
20    bvol::Aabb,
21    camera::Camera,
22    geometry::{Indices, MorphTarget, MorphTargetWeights, MorphTargets, Skin, Vertex, Vertices},
23    light::{shader::LightStyle, AnalyticalLight, Candela, Lux},
24    material::Material,
25    primitive::Primitive,
26    stage::{Stage, StageError},
27    transform::{shader::TransformDescriptor, NestedTransform},
28    types::{GpuCpuArray, GpuOnlyArray},
29};
30
31mod anime;
32pub use anime::*;
33
34#[derive(Debug, Snafu)]
35pub enum StageGltfError {
36    #[snafu(
37        display(
38            "GLTF error with '{}': {source}",
39            path
40                .as_ref()
41                .map(|p| p.display().to_string())
42                .unwrap_or("<bytes>".to_string())),
43        visibility(pub(crate))
44    )]
45    Gltf {
46        source: gltf::Error,
47        path: Option<std::path::PathBuf>,
48    },
49
50    #[snafu(display("{source}"))]
51    Atlas { source: crate::atlas::AtlasError },
52
53    #[snafu(display("Wrong image at index {index} atlas offset {offset}"))]
54    WrongImage { offset: usize, index: usize },
55
56    #[snafu(display("Missing image {index} '{name}'"))]
57    MissingImage { index: usize, name: String },
58
59    #[snafu(display("Missing texture at gltf index {index} slab index {tex_id:?}"))]
60    MissingTexture {
61        index: usize,
62        tex_id: Id<AtlasTextureDescriptor>,
63    },
64
65    #[snafu(display("Missing material with index {index}"))]
66    MissingMaterial { index: usize },
67
68    #[snafu(display("Missing primitive with index {index}"))]
69    MissingPrimitive { index: usize },
70
71    #[snafu(display("Missing mesh with index {index}"))]
72    MissingMesh { index: usize },
73
74    #[snafu(display("Missing node with index {index}"))]
75    MissingNode { index: usize },
76
77    #[snafu(display("Missing light with index {index}"))]
78    MissingLight { index: usize },
79
80    #[snafu(display("Unsupported primitive mode: {:?}", mode))]
81    PrimitiveMode { mode: gltf::mesh::Mode },
82
83    #[snafu(display("No {} attribute for mesh", attribute.to_string()))]
84    MissingAttribute { attribute: gltf::Semantic },
85
86    #[snafu(display("No weights array"))]
87    MissingWeights,
88
89    #[snafu(display("Missing sampler"))]
90    MissingSampler,
91
92    #[snafu(display("Missing gltf camera at index {index}"))]
93    MissingCamera { index: usize },
94
95    #[snafu(display("Node has no skin"))]
96    NoSkin,
97
98    #[snafu(display("Missing gltf skin at index {index}"))]
99    MissingSkin { index: usize },
100
101    #[snafu(display("{source}"))]
102    Animation { source: anime::AnimationError },
103}
104
105impl From<AtlasError> for StageGltfError {
106    fn from(source: AtlasError) -> Self {
107        Self::Atlas { source }
108    }
109}
110
111impl From<gltf::scene::Transform> for TransformDescriptor {
112    fn from(transform: gltf::scene::Transform) -> Self {
113        let (translation, rotation, scale) = transform.decomposed();
114        TransformDescriptor {
115            translation: Vec3::from_array(translation),
116            rotation: Quat::from_array(rotation),
117            scale: Vec3::from_array(scale),
118        }
119    }
120}
121
122pub fn from_gltf_light_kind(kind: gltf::khr_lights_punctual::Kind) -> LightStyle {
123    match kind {
124        gltf::khr_lights_punctual::Kind::Directional => LightStyle::Directional,
125        gltf::khr_lights_punctual::Kind::Point => LightStyle::Point,
126        gltf::khr_lights_punctual::Kind::Spot { .. } => LightStyle::Spot,
127    }
128}
129
130pub fn gltf_light_intensity_units(kind: gltf::khr_lights_punctual::Kind) -> &'static str {
131    match kind {
132        gltf::khr_lights_punctual::Kind::Directional => "lux (lm/m^2)",
133        // sr is "steradian"
134        _ => "candelas (lm/sr)",
135    }
136}
137
138impl TextureAddressMode {
139    fn from_gltf(mode: gltf::texture::WrappingMode) -> TextureAddressMode {
140        match mode {
141            gltf::texture::WrappingMode::ClampToEdge => TextureAddressMode::ClampToEdge,
142            gltf::texture::WrappingMode::MirroredRepeat => TextureAddressMode::MirroredRepeat,
143            gltf::texture::WrappingMode::Repeat => TextureAddressMode::Repeat,
144        }
145    }
146}
147
148pub fn get_vertex_count(primitive: &gltf::Primitive<'_>) -> u32 {
149    if let Some(indices) = primitive.indices() {
150        let count = indices.count() as u32;
151        log::trace!("    has {count} indices");
152        count
153    } else if let Some(positions) = primitive.get(&gltf::Semantic::Positions) {
154        let count = positions.count() as u32;
155        log::trace!("    has {count} positions");
156        count
157    } else {
158        log::trace!("    has no indices nor positions");
159        0
160    }
161}
162
163impl Material {
164    pub fn preprocess_images(
165        material: gltf::Material,
166        images: &mut [AtlasImage],
167    ) -> Result<(), StageGltfError> {
168        let pbr = material.pbr_metallic_roughness();
169        if material.unlit() {
170            if let Some(info) = pbr.base_color_texture() {
171                let texture = info.texture();
172                // The index of the image in the original gltf document
173                let image_index = texture.source().index();
174                let name = texture.name().unwrap_or("unknown");
175                // Update the image to ensure it gets transferred correctly
176                let image = images.get_mut(image_index).context(MissingImageSnafu {
177                    index: image_index,
178                    name,
179                })?;
180                image.apply_linear_transfer = true;
181            }
182        } else {
183            if let Some(info) = pbr.base_color_texture() {
184                let texture = info.texture();
185                let name = texture.name().unwrap_or("unknown");
186                let image_index = texture.source().index();
187                // Update the image to ensure it gets transferred correctly
188                let image = images.get_mut(image_index).context(MissingImageSnafu {
189                    index: image_index,
190                    name,
191                })?;
192                image.apply_linear_transfer = true;
193            }
194
195            if let Some(emissive_tex) = material.emissive_texture() {
196                let texture = emissive_tex.texture();
197                let name = texture.name().unwrap_or("unknown");
198                let image_index = texture.source().index();
199                // Update the image to ensure it gets transferred correctly
200                let image = images.get_mut(image_index).context(MissingImageSnafu {
201                    index: image_index,
202                    name,
203                })?;
204                image.apply_linear_transfer = true;
205            }
206        }
207        Ok(())
208    }
209
210    pub fn from_gltf(
211        stage: &Stage,
212        material: gltf::Material,
213        entries: &[AtlasTexture],
214    ) -> Result<Material, StageGltfError> {
215        let name = material.name().map(String::from);
216        log::trace!("loading material {:?} {name:?}", material.index());
217        let pbr = material.pbr_metallic_roughness();
218        let builder = stage.new_material();
219        if material.unlit() {
220            log::trace!("  is unlit");
221            builder.set_has_lighting(false);
222
223            if let Some(info) = pbr.base_color_texture() {
224                let texture = info.texture();
225                let index = texture.index();
226                if let Some(tex) = entries.get(index) {
227                    builder.set_albedo_texture(tex);
228                    builder.set_albedo_tex_coord(info.tex_coord());
229                }
230            }
231            builder.set_albedo_factor(pbr.base_color_factor().into());
232        } else {
233            log::trace!("  is pbr");
234            builder.set_has_lighting(true);
235
236            if let Some(info) = pbr.base_color_texture() {
237                let texture = info.texture();
238                let index = texture.index();
239                if let Some(tex) = entries.get(index) {
240                    builder.set_albedo_texture(tex);
241                    builder.set_albedo_tex_coord(info.tex_coord());
242                }
243            }
244            builder.set_albedo_factor(pbr.base_color_factor().into());
245
246            if let Some(info) = pbr.metallic_roughness_texture() {
247                let index = info.texture().index();
248                if let Some(tex) = entries.get(index) {
249                    builder.set_metallic_roughness_texture(tex);
250                    builder.set_metallic_roughness_tex_coord(info.tex_coord());
251                }
252            } else {
253                builder.set_metallic_factor(pbr.metallic_factor());
254                builder.set_roughness_factor(pbr.roughness_factor());
255            }
256
257            if let Some(norm_tex) = material.normal_texture() {
258                if let Some(tex) = entries.get(norm_tex.texture().index()) {
259                    builder.set_normal_texture(tex);
260                    builder.set_normal_tex_coord(norm_tex.tex_coord());
261                }
262            }
263
264            if let Some(occlusion_tex) = material.occlusion_texture() {
265                if let Some(tex) = entries.get(occlusion_tex.texture().index()) {
266                    builder.set_ambient_occlusion_texture(tex);
267                    builder.set_ambient_occlusion_tex_coord(occlusion_tex.tex_coord());
268                    builder.set_ambient_occlusion_strength(occlusion_tex.strength());
269                }
270            }
271
272            if let Some(emissive_tex) = material.emissive_texture() {
273                let texture = emissive_tex.texture();
274                let index = texture.index();
275                if let Some(tex) = entries.get(index) {
276                    builder.set_emissive_texture(tex);
277                    builder.set_emissive_tex_coord(emissive_tex.tex_coord());
278                }
279            }
280            builder.set_emissive_strength_multiplier(material.emissive_strength().unwrap_or(1.0));
281        };
282        Ok(builder)
283    }
284}
285
286pub struct GltfPrimitive<Ct: IsContainer = GpuCpuArray> {
287    pub indices: Indices<Ct>,
288    pub vertices: Vertices<Ct>,
289    pub bounding_box: (Vec3, Vec3),
290    pub material_index: Option<usize>,
291    pub morph_targets: MorphTargets,
292}
293
294impl<Ct> core::fmt::Debug for GltfPrimitive<Ct>
295where
296    Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
297    Ct: IsContainer<Pointer<u32> = Array<u32>>,
298{
299    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300        f.debug_struct("GltfPrimitive")
301            .field("indices", &self.indices)
302            .field("vertices", &self.vertices)
303            .field("bounding_box", &self.bounding_box)
304            .field("material_index", &self.material_index)
305            .field("morph_targets", &self.morph_targets)
306            .finish()
307    }
308}
309
310impl GltfPrimitive {
311    pub fn from_gltf(
312        stage: &Stage,
313        primitive: gltf::Primitive,
314        buffer_data: &[gltf::buffer::Data],
315    ) -> Self {
316        let material_index = primitive.material().index();
317
318        let reader = primitive.reader(|buffer| {
319            let data = buffer_data.get(buffer.index())?;
320            Some(data.0.as_slice())
321        });
322
323        let indices = reader
324            .read_indices()
325            .map(|is| {
326                let indices = is.into_u32().collect::<Vec<_>>();
327                assert_eq!(indices.len() % 3, 0, "indices do not form triangles");
328                indices
329            })
330            .unwrap_or_default();
331
332        let positions = reader
333            .read_positions()
334            .into_iter()
335            .flat_map(|ps| ps.map(Vec3::from))
336            .collect::<Vec<_>>();
337
338        let uv0s = reader
339            .read_tex_coords(0)
340            .into_iter()
341            .flat_map(|uvs| uvs.into_f32().map(Vec2::from))
342            .chain(std::iter::repeat(Vec2::ZERO))
343            .take(positions.len())
344            .collect::<Vec<_>>();
345
346        let uv1s = reader
347            .read_tex_coords(0)
348            .into_iter()
349            .flat_map(|uvs| uvs.into_f32().map(Vec2::from))
350            .chain(std::iter::repeat(Vec2::ZERO))
351            .take(positions.len());
352
353        let mut normals = vec![Vec3::Z; positions.len()];
354        if let Some(ns) = reader.read_normals() {
355            let ns = ns.map(Vec3::from).collect::<Vec<_>>();
356            debug_assert_eq!(positions.len(), ns.len());
357            normals = ns;
358        } else {
359            log::trace!("    generating normals");
360
361            let indices = if indices.is_empty() {
362                (0..positions.len() as u32).collect::<Vec<_>>()
363            } else {
364                indices.to_vec()
365            };
366
367            indices.chunks(3).for_each(|chunk| match chunk {
368                [i, j, k] => {
369                    let a = positions[*i as usize];
370                    let b = positions[*j as usize];
371                    let c = positions[*k as usize];
372                    let n = Vertex::generate_normal(a, b, c);
373                    normals[*i as usize] = n;
374                    normals[*j as usize] = n;
375                    normals[*k as usize] = n;
376                }
377                _ => panic!("not triangles!"),
378            });
379        }
380
381        let mut tangents = vec![Vec4::ZERO; positions.len()];
382        if let Some(ts) = reader.read_tangents() {
383            let ts = ts.map(Vec4::from).collect::<Vec<_>>();
384            debug_assert_eq!(positions.len(), ts.len());
385            tangents = ts;
386        } else {
387            log::trace!("    generating tangents");
388            let indices = if indices.is_empty() {
389                (0..positions.len() as u32).collect::<Vec<_>>()
390            } else {
391                indices.to_vec()
392            };
393
394            indices.chunks(3).for_each(|chunk| match chunk {
395                [i, j, k] => {
396                    let a = positions[*i as usize];
397                    let b = positions[*j as usize];
398                    let c = positions[*k as usize];
399                    let a_uv = uv0s[*i as usize];
400                    let b_uv = uv0s[*j as usize];
401                    let c_uv = uv0s[*k as usize];
402
403                    let t = Vertex::generate_tangent(a, a_uv, b, b_uv, c, c_uv);
404                    tangents[*i as usize] = t;
405                    tangents[*j as usize] = t;
406                    tangents[*k as usize] = t;
407                }
408                _ => panic!("not triangles!"),
409            });
410        }
411        let colors = reader
412            .read_colors(0)
413            .into_iter()
414            .flat_map(|cs| cs.into_rgba_f32().map(Vec4::from))
415            .chain(std::iter::repeat(Vec4::ONE))
416            .take(positions.len());
417
418        let joints = reader
419            .read_joints(0)
420            .into_iter()
421            .flat_map(|js| {
422                js.into_u16()
423                    .map(|[a, b, c, d]| [a as u32, b as u32, c as u32, d as u32])
424            })
425            .chain(std::iter::repeat([u32::MAX; 4]))
426            .take(positions.len());
427        let joints = joints.collect::<Vec<_>>();
428        let mut all_joints = FxHashSet::default();
429        for js in joints.iter() {
430            all_joints.extend(*js);
431        }
432        log::debug!("  joints: {all_joints:?}");
433
434        const UNWEIGHTED_WEIGHTS: [f32; 4] = [1.0, 0.0, 0.0, 0.0];
435        let mut logged_weights_not_f32 = false;
436        let weights = reader
437            .read_weights(0)
438            .into_iter()
439            .flat_map(|ws| {
440                if !logged_weights_not_f32 {
441                    match ws {
442                        gltf::mesh::util::ReadWeights::U8(_) => log::warn!("weights are u8"),
443                        gltf::mesh::util::ReadWeights::U16(_) => log::warn!("weights are u16"),
444                        gltf::mesh::util::ReadWeights::F32(_) => {}
445                    }
446                    logged_weights_not_f32 = true;
447                }
448                ws.into_f32().map(|weights| {
449                    // normalize the weights
450                    let sum = weights[0] + weights[1] + weights[2] + weights[3];
451                    weights.map(|w| w / sum)
452                })
453            })
454            .chain(std::iter::repeat(UNWEIGHTED_WEIGHTS))
455            .take(positions.len());
456
457        // See the GLTF spec on morph targets
458        // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets
459        //
460        // TODO: Generate morph target normals and tangents if absent.
461        // Although the spec says we have to generate normals or tangents if not specified,
462        // we are explicitly *not* doing that here.
463        let morph_targets: Vec<Vec<MorphTarget>> = reader
464            .read_morph_targets()
465            .map(|(may_ps, may_ns, may_ts)| {
466                let ps = may_ps
467                    .into_iter()
468                    .flat_map(|iter| iter.map(Vec3::from_array))
469                    .chain(std::iter::repeat(Vec3::ZERO))
470                    .take(positions.len());
471
472                let ns = may_ns
473                    .into_iter()
474                    .flat_map(|iter| iter.map(Vec3::from_array))
475                    .chain(std::iter::repeat(Vec3::ZERO))
476                    .take(positions.len());
477
478                let ts = may_ts
479                    .into_iter()
480                    .flat_map(|iter| iter.map(Vec3::from_array))
481                    .chain(std::iter::repeat(Vec3::ZERO))
482                    .take(positions.len());
483
484                ps.zip(ns)
485                    .zip(ts)
486                    .map(|((position, normal), tangent)| MorphTarget {
487                        position,
488                        normal,
489                        tangent,
490                    })
491                    .collect()
492            })
493            .collect();
494        log::debug!(
495            "  {} morph_targets: {:?}",
496            morph_targets.len(),
497            morph_targets.iter().map(|mt| mt.len()).collect::<Vec<_>>()
498        );
499        let morph_targets = stage.new_morph_targets(morph_targets);
500        let vs = joints.into_iter().zip(weights);
501        let vs = colors.zip(vs);
502        let vs = tangents.into_iter().zip(vs);
503        let vs = normals.into_iter().zip(vs);
504        let vs = uv1s.zip(vs);
505        let vs = uv0s.into_iter().zip(vs);
506        let vs = positions.into_iter().zip(vs);
507
508        let mut min = Vec3::splat(f32::INFINITY);
509        let mut max = Vec3::splat(f32::NEG_INFINITY);
510        let vertices = vs
511            .map(
512                |(position, (uv0, (uv1, (normal, (tangent, (color, (joints, weights)))))))| {
513                    min = min.min(position);
514                    max = max.max(position);
515                    Vertex {
516                        position,
517                        color,
518                        uv0,
519                        uv1,
520                        normal,
521                        tangent,
522                        joints,
523                        weights,
524                    }
525                },
526            )
527            .collect::<Vec<_>>();
528        let vertices = stage.new_vertices(vertices);
529        log::debug!(
530            "{} vertices, {:?}",
531            vertices.array().len(),
532            vertices.array()
533        );
534        let indices = stage.new_indices(indices);
535        log::debug!("{} indices, {:?}", indices.array().len(), indices.array());
536        let (bbmin, bbmax) = {
537            let gltf::mesh::Bounds { min, max } = primitive.bounding_box();
538            (Vec3::from_array(min), Vec3::from_array(max))
539        };
540        if bbmin != min {
541            log::warn!("gltf supplied bounding box min ({bbmin:?}) doesn't match seen ({min:?})");
542        }
543        if bbmax != max {
544            log::warn!("gltf supplied bounding box max ({bbmax:?}) doesn't match seen ({max:?})");
545        }
546        let bounding_box = (min, max);
547
548        log::info!("primitive '{}' bounds: {bounding_box:?}", primitive.index());
549
550        Self {
551            vertices,
552            indices,
553            material_index,
554            morph_targets,
555            bounding_box,
556        }
557    }
558
559    pub fn into_gpu_only(self) -> GltfPrimitive<GpuOnlyArray> {
560        let Self {
561            indices,
562            vertices,
563            bounding_box,
564            material_index,
565            morph_targets,
566        } = self;
567        GltfPrimitive {
568            indices: indices.into_gpu_only(),
569            vertices: vertices.into_gpu_only(),
570            bounding_box,
571            material_index,
572            morph_targets,
573        }
574    }
575}
576
577pub struct GltfMesh<Ct: IsContainer = GpuCpuArray> {
578    /// Mesh primitives, aka meshlets
579    pub primitives: Vec<GltfPrimitive<Ct>>,
580    /// Morph target weights
581    pub weights: MorphTargetWeights,
582}
583
584impl<Ct> core::fmt::Debug for GltfMesh<Ct>
585where
586    Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
587    Ct: IsContainer<Pointer<u32> = Array<u32>>,
588{
589    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
590        f.debug_struct("GltfMesh")
591            .field("primitives", &self.primitives)
592            .field("weights", &self.weights)
593            .finish()
594    }
595}
596
597impl GltfMesh {
598    fn from_gltf(stage: &Stage, buffer_data: &[gltf::buffer::Data], mesh: gltf::Mesh) -> Self {
599        log::debug!("Loading primitives for mesh {}", mesh.index());
600        let primitives = mesh
601            .primitives()
602            .map(|prim| GltfPrimitive::from_gltf(stage, prim, buffer_data))
603            .collect::<Vec<_>>();
604        log::debug!("  loaded {} primitives\n", primitives.len());
605        let weights = mesh.weights().unwrap_or(&[]).iter().copied();
606        GltfMesh {
607            primitives,
608            weights: stage.new_morph_target_weights(weights),
609        }
610    }
611
612    pub fn into_gpu_only(self) -> GltfMesh<GpuOnlyArray> {
613        let Self {
614            primitives,
615            weights,
616        } = self;
617        let primitives = primitives
618            .into_iter()
619            .map(GltfPrimitive::into_gpu_only)
620            .collect::<Vec<_>>();
621        GltfMesh {
622            primitives,
623            weights,
624        }
625    }
626}
627
628#[derive(Debug)]
629pub struct GltfCamera {
630    pub index: usize,
631    pub name: Option<String>,
632    pub node_transform: NestedTransform,
633    pub camera: Camera,
634}
635
636impl AsRef<Camera> for GltfCamera {
637    fn as_ref(&self) -> &Camera {
638        &self.camera
639    }
640}
641
642impl GltfCamera {
643    fn new(stage: &Stage, gltf_camera: gltf::Camera<'_>, transform: &NestedTransform) -> Self {
644        log::debug!("camera: {}", gltf_camera.name().unwrap_or("unknown"));
645        log::debug!("  transform: {:#?}", transform.global_descriptor());
646        let projection = match gltf_camera.projection() {
647            gltf::camera::Projection::Orthographic(o) => glam::Mat4::orthographic_rh(
648                -o.xmag(),
649                o.xmag(),
650                -o.ymag(),
651                o.ymag(),
652                o.znear(),
653                o.zfar(),
654            ),
655            gltf::camera::Projection::Perspective(p) => {
656                let fovy = p.yfov();
657                let aspect = p.aspect_ratio().unwrap_or(1.0);
658                if let Some(zfar) = p.zfar() {
659                    glam::Mat4::perspective_rh(fovy, aspect, p.znear(), zfar)
660                } else {
661                    glam::Mat4::perspective_infinite_rh(
662                        p.yfov(),
663                        p.aspect_ratio().unwrap_or(1.0),
664                        p.znear(),
665                    )
666                }
667            }
668        };
669        let view = Mat4::from(transform.global_descriptor()).inverse();
670        let camera = stage
671            .new_camera()
672            .with_projection_and_view(projection, view);
673        // what else can we get out of the camera
674        let extensions = gltf_camera.extensions();
675        log::debug!("  extensions: {extensions:#?}");
676        let extras = gltf_camera.extras();
677        log::debug!("  extras: {extras:#?}");
678        GltfCamera {
679            index: gltf_camera.index(),
680            name: gltf_camera.name().map(String::from),
681            node_transform: transform.clone(),
682            camera,
683        }
684    }
685}
686
687/// A node in a GLTF document, ready to be 'drawn'.
688#[derive(Clone, Debug)]
689pub struct GltfNode {
690    /// Index of this node in the `StagedGltfDocument`'s `nodes` field.
691    pub index: usize,
692    /// Name of the node, if any,
693    pub name: Option<String>,
694    /// Id of the light this node refers to.
695    pub light: Option<usize>,
696    /// Index of the mesh in the document's meshes, if any.
697    pub mesh: Option<usize>,
698    /// Index into the cameras array, if any.
699    pub camera: Option<usize>,
700    /// Index of the skin in the document's skins, if any.
701    pub skin: Option<usize>,
702    /// Indices of the children of this node.
703    ///
704    /// Each element indexes into the `GltfDocument`'s `nodes` field.
705    pub children: Vec<usize>,
706    /// Morph target weights
707    pub weights: MorphTargetWeights,
708    /// This node's transform.
709    pub transform: NestedTransform,
710}
711
712impl GltfNode {
713    pub fn global_transform(&self) -> TransformDescriptor {
714        self.transform.global_descriptor()
715    }
716}
717
718#[derive(Clone, Debug)]
719pub struct GltfSkin {
720    pub index: usize,
721    // Indices of the skeleton nodes used as joints in this skin, unused internally
722    // but possibly useful.
723    pub joint_nodes: Vec<usize>,
724    // Index of the node used as the skeleton root.
725    // When None, joints transforms resolve to scene root.
726    pub skeleton: Option<usize>,
727    // Skin as seen by renderling
728    pub skin: Skin,
729}
730
731impl GltfSkin {
732    pub fn from_gltf(
733        stage: &Stage,
734        buffer_data: &[gltf::buffer::Data],
735        nodes: &[GltfNode],
736        gltf_skin: gltf::Skin,
737    ) -> Result<Self, StageGltfError> {
738        log::debug!("reading skin {} {:?}", gltf_skin.index(), gltf_skin.name());
739        let joint_nodes = gltf_skin.joints().map(|n| n.index()).collect::<Vec<_>>();
740        log::debug!("  has {} joints", joint_nodes.len());
741        let mut joint_transforms = vec![];
742        for node_index in joint_nodes.iter() {
743            let gltf_node: &GltfNode = nodes
744                .get(*node_index)
745                .context(MissingNodeSnafu { index: *node_index })?;
746            let transform_id = gltf_node.transform.global_descriptor();
747            log::debug!("    joint node {node_index} is {transform_id:?}");
748            joint_transforms.push(gltf_node.transform.clone());
749        }
750        let reader = gltf_skin.reader(|b| buffer_data.get(b.index()).map(|d| d.0.as_slice()));
751        let mut inverse_bind_matrices = vec![];
752        if let Some(mats) = reader.read_inverse_bind_matrices() {
753            let invs = mats
754                .into_iter()
755                .map(|m| Mat4::from_cols_array_2d(&m))
756                .collect::<Vec<_>>();
757            log::debug!("  has {} inverse bind matrices", invs.len());
758            inverse_bind_matrices = invs;
759        } else {
760            log::debug!("  no inverse bind matrices");
761        }
762        let skeleton = if let Some(n) = gltf_skin.skeleton() {
763            let index = n.index();
764            log::debug!("  skeleton is node {index}, {:?}", n.name());
765            Some(index)
766        } else {
767            log::debug!("  skeleton is assumed to be the scene root");
768            None
769        };
770        Ok(GltfSkin {
771            index: gltf_skin.index(),
772            skin: stage.new_skin(joint_transforms, inverse_bind_matrices),
773            joint_nodes,
774            skeleton,
775        })
776    }
777}
778
779/// A loaded GLTF document.
780///
781/// After being loaded, a [`GltfDocument`] is a collection of staged resources.
782///
783/// All primitives are automatically added to the [`Stage`] they were loaded
784/// from.
785///
786/// ## Note
787///
788/// After being loaded, the `meshes` field contains [`Vertices`] and [`Indices`]
789/// that can be inspected from the CPU. This has memory implications, so if your
790/// document contains lots of geometric data it is advised that you unload that
791/// data from the CPU using [`GltfDocument::into_gpu_only`].
792pub struct GltfDocument<Ct: IsContainer = GpuCpuArray> {
793    pub animations: Vec<Animation>,
794    pub cameras: Vec<GltfCamera>,
795    pub default_scene: Option<usize>,
796    pub extensions: Option<serde_json::Value>,
797    pub textures: Vec<AtlasTexture>,
798    pub lights: Vec<AnalyticalLight>,
799    pub meshes: Vec<GltfMesh<Ct>>,
800    pub nodes: Vec<GltfNode>,
801    pub default_material: Material,
802    pub materials: Vec<Material>,
803    // map of node index to primitives
804    pub primitives: FxHashMap<usize, Vec<Primitive>>,
805    /// Vector of scenes - each being a list of nodes.
806    pub scenes: Vec<Vec<usize>>,
807    pub skins: Vec<GltfSkin>,
808}
809
810impl GltfDocument {
811    pub fn from_gltf(
812        stage: &Stage,
813        document: &gltf::Document,
814        buffer_data: Vec<gltf::buffer::Data>,
815        images: Vec<gltf::image::Data>,
816    ) -> Result<GltfDocument, StageError> {
817        let textures = {
818            let mut images = images.into_iter().map(AtlasImage::from).collect::<Vec<_>>();
819            for gltf_material in document.materials() {
820                Material::preprocess_images(gltf_material, &mut images)?;
821            }
822            // Arc these images because they could be large and we don't want duplicates
823            let images = images.into_iter().map(Arc::new).collect::<Vec<_>>();
824
825            log::debug!("Loading {} images into the atlas", images.len());
826
827            log::debug!("Writing textures");
828            #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
829            struct Texture {
830                source: usize,
831                modes: TextureModes,
832            }
833            let mut textures = vec![];
834            let mut deduped_textures = FxHashMap::<Texture, Vec<usize>>::default();
835            for (i, texture) in document.textures().enumerate() {
836                let index = texture.index();
837                debug_assert_eq!(i, index);
838                let name = texture.name().unwrap_or("unknown");
839                log::trace!("  texture {i} '{name}'",);
840                let source = texture.source().index();
841                let modes = TextureModes {
842                    s: TextureAddressMode::from_gltf(texture.sampler().wrap_s()),
843                    t: TextureAddressMode::from_gltf(texture.sampler().wrap_t()),
844                };
845                let tex = Texture { modes, source };
846                textures.push(tex);
847                let entry = deduped_textures.entry(tex).or_default();
848                entry.push(i);
849            }
850
851            // Prepare the textures for packing
852            let mut deduped_textures = deduped_textures.into_iter().collect::<Vec<_>>();
853            deduped_textures.sort();
854            let mut prepared_images = vec![];
855            for (tex, refs) in deduped_textures.iter() {
856                let image = images
857                    .get(tex.source)
858                    .context(MissingImageSnafu {
859                        index: refs[0],
860                        name: "unknown".to_owned(),
861                    })?
862                    .clone();
863                prepared_images.push(image);
864            }
865            let duplicated_image_count = prepared_images.len() - images.len();
866            if duplicated_image_count > 0 {
867                log::debug!("had to duplicate {duplicated_image_count} images...");
868            }
869            drop(images);
870
871            let prepared_images: Vec<AtlasImage> = prepared_images
872                .into_iter()
873                .map(|aimg| match Arc::try_unwrap(aimg) {
874                    Ok(img) => img,
875                    Err(aimg) => aimg.as_ref().clone(),
876                })
877                .collect();
878            let hybrid_textures = stage.add_images(prepared_images)?;
879            let mut texture_lookup = FxHashMap::<usize, AtlasTexture>::default();
880            for (hybrid, (tex, refs)) in hybrid_textures.into_iter().zip(deduped_textures) {
881                hybrid.set_modes(tex.modes);
882                for tex_index in refs.into_iter() {
883                    texture_lookup.insert(tex_index, hybrid.clone());
884                }
885            }
886            let mut textures = texture_lookup.into_iter().collect::<Vec<_>>();
887            textures.sort_by_key(|(index, _)| *index);
888            textures
889                .into_iter()
890                .map(|(_, hybrid)| hybrid)
891                .collect::<Vec<_>>()
892        };
893
894        log::debug!("Creating materials");
895        let mut default_material = stage.default_material().clone();
896        let mut materials = vec![];
897        for gltf_material in document.materials() {
898            let material_index = gltf_material.index();
899            let material = Material::from_gltf(stage, gltf_material, &textures)?;
900            if let Some(index) = material_index {
901                log::trace!("  created material {index}");
902                debug_assert_eq!(index, materials.len(), "unexpected material index");
903                materials.push(material);
904            } else {
905                log::trace!("  created default material");
906                default_material = material;
907            }
908        }
909        log::trace!("  created {} materials", materials.len());
910
911        log::debug!("Loading meshes");
912        let mut meshes = vec![];
913        for mesh in document.meshes() {
914            let mesh = GltfMesh::from_gltf(stage, &buffer_data, mesh);
915            meshes.push(mesh);
916        }
917        log::trace!("  loaded {} meshes", meshes.len());
918
919        log::debug!("Loading {} nodes", document.nodes().count());
920        let mut nodes = vec![];
921        let mut node_transforms = HashMap::<usize, NestedTransform>::new();
922
923        fn transform_for_node(
924            nesting_level: usize,
925            stage: &Stage,
926            cache: &mut HashMap<usize, NestedTransform>,
927            node: &gltf::Node,
928        ) -> NestedTransform {
929            let padding = std::iter::repeat_n(" ", nesting_level * 2)
930                .collect::<Vec<_>>()
931                .join("");
932            let nt = if let Some(nt) = cache.get(&node.index()) {
933                nt.clone()
934            } else {
935                let TransformDescriptor {
936                    translation,
937                    rotation,
938                    scale,
939                } = node.transform().into();
940                let transform = stage
941                    .new_nested_transform()
942                    .with_local_translation(translation)
943                    .with_local_rotation(rotation)
944                    .with_local_scale(scale);
945                for node in node.children() {
946                    let child_transform =
947                        transform_for_node(nesting_level + 1, stage, cache, &node);
948                    transform.add_child(&child_transform);
949                }
950                cache.insert(node.index(), transform.clone());
951                transform
952            };
953            let t = nt.local_descriptor();
954            log::trace!(
955                "{padding}{} {:?} {:?} {:?} {:?}",
956                node.index(),
957                node.name(),
958                t.translation,
959                t.rotation,
960                t.scale
961            );
962            nt
963        }
964        let mut camera_index_to_node_index = HashMap::<usize, usize>::new();
965        let mut light_index_to_node_index = HashMap::<usize, usize>::new();
966        for (i, node) in document.nodes().enumerate() {
967            let node_index = node.index();
968            if let Some(camera) = node.camera() {
969                camera_index_to_node_index.insert(camera.index(), node_index);
970            }
971            if let Some(light) = node.light() {
972                light_index_to_node_index.insert(light.index(), node_index);
973            }
974
975            debug_assert_eq!(i, node_index);
976            let children = node.children().map(|node| node.index()).collect::<Vec<_>>();
977            let mesh = node.mesh().map(|mesh| mesh.index());
978            let skin = node.skin().map(|skin| skin.index());
979            let camera = node.camera().map(|camera| camera.index());
980            let light = node.light().map(|light| light.index());
981            let weights = node.weights().map(|w| w.to_vec()).unwrap_or_default();
982            // From the glTF spec:
983            //
984            // A mesh with morph targets MAY also define an optional mesh.weights property
985            // that stores the default targets' weights. These weights MUST be used when
986            // node.weights is undefined. When mesh.weights is undefined, the default
987            // targets' weights are zeros.
988            let weights = if weights.is_empty() {
989                if let Some(mesh) = node.mesh() {
990                    meshes[mesh.index()].weights.clone()
991                } else {
992                    stage.new_morph_target_weights(weights)
993                }
994            } else {
995                stage.new_morph_target_weights(weights)
996            };
997            let transform = transform_for_node(0, stage, &mut node_transforms, &node);
998            nodes.push(GltfNode {
999                index: node.index(),
1000                name: node.name().map(String::from),
1001                light,
1002                mesh,
1003                camera,
1004                skin,
1005                children,
1006                weights,
1007                transform,
1008            });
1009        }
1010        log::trace!("  loaded {} nodes", nodes.len());
1011
1012        log::trace!("Loading cameras");
1013        let mut cameras = vec![];
1014        for camera in document.cameras() {
1015            let camera_index = camera.index();
1016            let node_index =
1017                *camera_index_to_node_index
1018                    .get(&camera_index)
1019                    .context(MissingCameraSnafu {
1020                        index: camera_index,
1021                    })?;
1022            let transform = node_transforms
1023                .get(&node_index)
1024                .context(MissingNodeSnafu { index: node_index })?;
1025            cameras.push(GltfCamera::new(stage, camera, transform));
1026        }
1027
1028        log::trace!("Loading lights");
1029        let mut lights = vec![];
1030        if let Some(gltf_lights) = document.lights() {
1031            for gltf_light in gltf_lights {
1032                let node_index = *light_index_to_node_index.get(&gltf_light.index()).context(
1033                    MissingCameraSnafu {
1034                        index: gltf_light.index(),
1035                    },
1036                )?;
1037
1038                let node_transform = node_transforms
1039                    .get(&node_index)
1040                    .context(MissingNodeSnafu { index: node_index })?
1041                    .clone();
1042
1043                let color = Vec3::from(gltf_light.color()).extend(1.0);
1044                let intensity = gltf_light.intensity();
1045                let light_bundle = match gltf_light.kind() {
1046                    gltf::khr_lights_punctual::Kind::Directional => {
1047                        stage
1048                            .new_directional_light()
1049                            .with_direction(Vec3::NEG_Z)
1050                            .with_color(color)
1051                            // Assumed to be lux
1052                            .with_intensity(Lux(intensity))
1053                            .into_generic()
1054                    }
1055
1056                    gltf::khr_lights_punctual::Kind::Point => stage
1057                        .new_point_light()
1058                        .with_position(Vec3::ZERO)
1059                        .with_color(color)
1060                        // Assumed to be Candelas.
1061                        //
1062                        // This is converted to radiometric units in the PBR shader.
1063                        .with_intensity(Candela(intensity))
1064                        .into_generic(),
1065
1066                    gltf::khr_lights_punctual::Kind::Spot {
1067                        inner_cone_angle,
1068                        outer_cone_angle,
1069                    } => stage
1070                        .new_spot_light()
1071                        .with_position(Vec3::ZERO)
1072                        .with_direction(Vec3::NEG_Z)
1073                        .with_inner_cutoff(inner_cone_angle)
1074                        .with_outer_cutoff(outer_cone_angle)
1075                        .with_color(color)
1076                        // Assumed to be Candelas.
1077                        //
1078                        // This is converted to radiometric units in the PBR shader.
1079                        .with_intensity(Candela(intensity))
1080                        .into_generic(),
1081                };
1082
1083                log::trace!(
1084                    "  linking light {:?} with node transform {:?}: {:#?}",
1085                    light_bundle.id(),
1086                    node_transform.global_id(),
1087                    node_transform.global_descriptor()
1088                );
1089                light_bundle.link_node_transform(&node_transform);
1090                lights.push(light_bundle);
1091            }
1092        }
1093
1094        log::trace!("Loading skins");
1095        let mut skins = vec![];
1096        for skin in document.skins() {
1097            skins.push(GltfSkin::from_gltf(stage, &buffer_data, &nodes, skin)?);
1098        }
1099
1100        log::trace!("Loading animations");
1101        let mut animations = vec![];
1102        for animation in document.animations() {
1103            animations.push(Animation::from_gltf(&buffer_data, animation).context(AnimationSnafu)?);
1104        }
1105
1106        log::debug!("Loading scenes");
1107        let scenes = document
1108            .scenes()
1109            .map(|scene| scene.nodes().map(|node| node.index()).collect())
1110            .collect();
1111
1112        log::debug!("Creating renderlets");
1113        let mut renderlets = FxHashMap::default();
1114        for gltf_node in nodes.iter() {
1115            let mut node_renderlets = vec![];
1116            let maybe_skin = if let Some(skin_index) = gltf_node.skin {
1117                log::debug!("  node {} {:?} has skin", gltf_node.index, gltf_node.name);
1118                let gltf_skin = skins
1119                    .get(skin_index)
1120                    .context(MissingSkinSnafu { index: skin_index })?;
1121                Some(gltf_skin.skin.clone())
1122            } else {
1123                None
1124            };
1125
1126            if let Some(mesh_index) = gltf_node.mesh {
1127                log::trace!(
1128                    "  node {} {:?} has mesh {mesh_index}",
1129                    gltf_node.index,
1130                    gltf_node.name
1131                );
1132                let mesh = meshes
1133                    .get(mesh_index)
1134                    .context(MissingMeshSnafu { index: mesh_index })?;
1135                let num_prims = mesh.primitives.len();
1136                log::trace!("    has {num_prims} primitives");
1137                for (prim, i) in mesh.primitives.iter().zip(1..) {
1138                    let material = prim
1139                        .material_index
1140                        .and_then(|index| materials.get(index))
1141                        .unwrap_or(&default_material);
1142                    let renderlet = stage
1143                        .new_primitive()
1144                        .with_vertices(&prim.vertices)
1145                        .with_indices(&prim.indices)
1146                        .with_transform(&gltf_node.transform)
1147                        .with_material(material)
1148                        .with_bounds(prim.bounding_box.into())
1149                        .with_morph_targets(&prim.morph_targets, &gltf_node.weights);
1150                    if let Some(skin) = maybe_skin.as_ref() {
1151                        renderlet.set_skin(skin);
1152                    }
1153                    log::trace!(
1154                        "    created renderlet {i}/{num_prims}: {:#?}",
1155                        renderlet.id()
1156                    );
1157                    node_renderlets.push(renderlet);
1158                }
1159            }
1160            if !node_renderlets.is_empty() {
1161                renderlets.insert(gltf_node.index, node_renderlets);
1162            }
1163        }
1164
1165        log::debug!("Extensions used: {:?}", document.extensions_used());
1166        log::debug!("Extensions required: {:?}", document.extensions_required());
1167        log::debug!("Done loading gltf");
1168
1169        Ok(GltfDocument {
1170            textures,
1171            animations,
1172            lights,
1173            cameras,
1174            materials,
1175            default_material,
1176            meshes,
1177            nodes,
1178            scenes,
1179            skins,
1180            default_scene: document.default_scene().map(|scene| scene.index()),
1181            primitives: renderlets,
1182            extensions: document
1183                .extensions()
1184                .cloned()
1185                .map(serde_json::Value::Object),
1186        })
1187    }
1188
1189    /// Unload vertex and index data from the CPU.
1190    ///
1191    /// The data can still be updated from the CPU, but will not be inspectable.
1192    pub fn into_gpu_only(self) -> GltfDocument<GpuOnlyArray> {
1193        let Self {
1194            animations,
1195            cameras,
1196            default_scene,
1197            extensions,
1198            textures,
1199            lights,
1200            meshes,
1201            nodes,
1202            default_material,
1203            materials,
1204            primitives,
1205            scenes,
1206            skins,
1207        } = self;
1208        let meshes = meshes
1209            .into_iter()
1210            .map(GltfMesh::into_gpu_only)
1211            .collect::<Vec<_>>();
1212        GltfDocument {
1213            animations,
1214            cameras,
1215            default_scene,
1216            extensions,
1217            textures,
1218            lights,
1219            meshes,
1220            nodes,
1221            default_material,
1222            materials,
1223            primitives,
1224            scenes,
1225            skins,
1226        }
1227    }
1228}
1229
1230impl<Ct> GltfDocument<Ct>
1231where
1232    Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
1233    Ct: IsContainer<Pointer<u32> = Array<u32>>,
1234{
1235    pub fn renderlets_iter(&self) -> impl Iterator<Item = &Primitive> {
1236        self.primitives.iter().flat_map(|(_, rs)| rs.iter())
1237    }
1238
1239    pub fn nodes_in_scene(&self, scene_index: usize) -> impl Iterator<Item = &GltfNode> {
1240        let scene = self.scenes.get(scene_index);
1241        let mut nodes = vec![];
1242        if let Some(indices) = scene {
1243            for node_index in indices {
1244                if let Some(node) = self.nodes.get(*node_index) {
1245                    nodes.push(node);
1246                }
1247            }
1248        }
1249        nodes.into_iter()
1250    }
1251
1252    /// Returns the bounding volume of this document, if possible.
1253    ///
1254    /// This function will return `None` if this document does not contain meshes.
1255    pub fn bounding_volume(&self) -> Option<Aabb> {
1256        let mut aabbs = vec![];
1257        for node in self.nodes.iter() {
1258            if let Some(mesh_index) = node.mesh {
1259                let mesh = self.meshes.get(mesh_index)?;
1260                for prim in mesh.primitives.iter() {
1261                    let (prim_min, prim_max) = prim.bounding_box;
1262                    let prim_aabb = Aabb::new(prim_min, prim_max);
1263                    aabbs.push(prim_aabb);
1264                }
1265            }
1266        }
1267        let mut aabbs = aabbs.into_iter();
1268        let mut aabb = aabbs.next()?;
1269        for next_aabb in aabbs {
1270            aabb = Aabb::union(aabb, next_aabb);
1271        }
1272        Some(aabb)
1273    }
1274}
1275
1276#[cfg(test)]
1277mod test {
1278    use crate::{context::Context, geometry::Vertex, test::BlockOnFuture};
1279    use glam::{Vec3, Vec4};
1280
1281    #[test]
1282    fn get_vertex_count_primitive_sanity() {
1283        let (document, _, _) =
1284            gltf::import("../../gltf/gltfTutorial_008_SimpleMeshes.gltf").unwrap();
1285        let prim = document
1286            .meshes()
1287            .next()
1288            .unwrap()
1289            .primitives()
1290            .next()
1291            .unwrap();
1292        let vertex_count = super::get_vertex_count(&prim);
1293        assert_eq!(3, vertex_count);
1294    }
1295
1296    #[test]
1297    // ensures we can
1298    // * read simple meshes
1299    // * support multiple nodes that reference the same mesh
1300    // * support primitives w/ positions and normal attributes
1301    // * support transforming nodes (T * R * S)
1302    fn stage_gltf_simple_meshes() {
1303        let ctx = Context::headless(100, 50).block();
1304        let projection = crate::camera::perspective(100.0, 50.0);
1305        let position = Vec3::new(1.0, 0.5, 1.5);
1306        let view = crate::camera::look_at(position, Vec3::new(1.0, 0.5, 0.0), Vec3::Y);
1307        let stage = ctx
1308            .new_stage()
1309            .with_lighting(false)
1310            .with_bloom(false)
1311            .with_background_color(Vec3::splat(0.0).extend(1.0));
1312        let _camera = stage
1313            .new_camera()
1314            .with_projection_and_view(projection, view);
1315        let _doc = stage
1316            .load_gltf_document_from_path("../../gltf/gltfTutorial_008_SimpleMeshes.gltf")
1317            .unwrap();
1318
1319        let frame = ctx.get_next_frame().unwrap();
1320        stage.render(&frame.view());
1321        let img = frame.read_image().block().unwrap();
1322        img_diff::assert_img_eq("gltf/simple_meshes.png", img);
1323    }
1324
1325    #[test]
1326    // Ensures we can read a minimal gltf file with a simple triangle mesh.
1327    fn minimal_mesh() {
1328        let ctx = Context::headless(20, 20).block();
1329        let stage = ctx
1330            .new_stage()
1331            .with_lighting(false)
1332            .with_bloom(false)
1333            .with_background_color(Vec3::splat(0.0).extend(1.0));
1334
1335        let projection = crate::camera::perspective(20.0, 20.0);
1336        let eye = Vec3::new(0.5, 0.5, 2.0);
1337        let view = crate::camera::look_at(eye, Vec3::new(0.5, 0.5, 0.0), Vec3::Y);
1338        let _camera = stage
1339            .new_camera()
1340            .with_projection_and_view(projection, view);
1341
1342        let _doc = stage
1343            .load_gltf_document_from_path("../../gltf/gltfTutorial_003_MinimalGltfFile.gltf")
1344            .unwrap();
1345
1346        let frame = ctx.get_next_frame().unwrap();
1347        stage.render(&frame.view());
1348        let img = frame.read_image().block().unwrap();
1349        img_diff::assert_img_eq("gltf/minimal_mesh.png", img);
1350    }
1351
1352    #[test]
1353    // Tests importing a gltf file and rendering the first image as a 2d object.
1354    //
1355    // This ensures we are decoding images correctly.
1356    fn gltf_images() {
1357        let ctx = Context::headless(100, 100).block();
1358        let stage = ctx
1359            .new_stage()
1360            .with_lighting(false)
1361            .with_background_color(Vec4::splat(1.0));
1362        let (projection, view) = crate::camera::default_ortho2d(100.0, 100.0);
1363        let _camera = stage
1364            .new_camera()
1365            .with_projection_and_view(projection, view);
1366        let doc = stage
1367            .load_gltf_document_from_path("../../gltf/cheetah_cone.glb")
1368            .unwrap();
1369        assert!(!doc.textures.is_empty());
1370        let material = stage
1371            .new_material()
1372            .with_albedo_texture(&doc.textures[0])
1373            .with_has_lighting(false);
1374        let _rez = stage
1375            .new_primitive()
1376            .with_material(&material)
1377            .with_vertices(
1378                stage.new_vertices([
1379                    Vertex::default()
1380                        .with_position([0.0, 0.0, 0.0])
1381                        .with_uv0([0.0, 0.0]),
1382                    Vertex::default()
1383                        .with_position([1.0, 0.0, 0.0])
1384                        .with_uv0([1.0, 0.0]),
1385                    Vertex::default()
1386                        .with_position([1.0, 1.0, 0.0])
1387                        .with_uv0([1.0, 1.0]),
1388                    Vertex::default()
1389                        .with_position([0.0, 1.0, 0.0])
1390                        .with_uv0([0.0, 1.0]),
1391                ]),
1392            )
1393            .with_indices(stage.new_indices([0u32, 3, 2, 0, 2, 1]))
1394            .with_transform(
1395                stage
1396                    .new_transform()
1397                    .with_scale(Vec3::new(100.0, 100.0, 1.0)),
1398            );
1399        println!("material_id: {:#?}", material.id());
1400
1401        let frame = ctx.get_next_frame().unwrap();
1402        stage.render(&frame.view());
1403        let img = frame.read_linear_image().block().unwrap();
1404        img_diff::assert_img_eq("gltf/images.png", img);
1405    }
1406
1407    #[test]
1408    fn simple_texture() {
1409        let size = 100;
1410        let ctx = Context::headless(size, size).block();
1411        let stage = ctx
1412            .new_stage()
1413            .with_background_color(Vec3::splat(0.0).extend(1.0))
1414            // There are no lights in the scene and the material isn't marked as "unlit", so
1415            // let's force it to be unlit.
1416            .with_lighting(false)
1417            .with_bloom(false);
1418        let projection = crate::camera::perspective(size as f32, size as f32);
1419        let view =
1420            crate::camera::look_at(Vec3::new(0.5, 0.5, 1.25), Vec3::new(0.5, 0.5, 0.0), Vec3::Y);
1421        let _camera = stage
1422            .new_camera()
1423            .with_projection_and_view(projection, view);
1424
1425        let _doc = stage
1426            .load_gltf_document_from_path("../../gltf/gltfTutorial_013_SimpleTexture.gltf")
1427            .unwrap();
1428
1429        let frame = ctx.get_next_frame().unwrap();
1430        stage.render(&frame.view());
1431        let img = frame.read_image().block().unwrap();
1432        img_diff::assert_img_eq("gltf/simple_texture.png", img);
1433    }
1434
1435    #[test]
1436    // Demonstrates how to load and render a gltf file containing lighting and a
1437    // normal map.
1438    fn normal_mapping_brick_sphere() {
1439        let ctx = Context::headless(1920, 1080).block();
1440        let stage = ctx
1441            .new_stage()
1442            .with_lighting(true)
1443            .with_background_color(Vec4::new(0.01, 0.01, 0.01, 1.0));
1444
1445        let _doc = stage
1446            .load_gltf_document_from_path("../../gltf/normal_mapping_brick_sphere.glb")
1447            .unwrap();
1448
1449        let frame = ctx.get_next_frame().unwrap();
1450        stage.render(&frame.view());
1451        let img = frame.read_image().block().unwrap();
1452        img_diff::assert_img_eq("gltf/normal_mapping_brick_sphere.png", img);
1453    }
1454
1455    #[test]
1456    fn rigged_fox() {
1457        let ctx = Context::headless(256, 256).block();
1458        let stage = ctx
1459            .new_stage()
1460            .with_lighting(false)
1461            .with_vertex_skinning(false)
1462            .with_bloom(false)
1463            .with_background_color(Vec3::splat(0.5).extend(1.0));
1464
1465        let aspect = 256.0 / 256.0;
1466        let fovy = core::f32::consts::PI / 4.0;
1467        let znear = 0.1;
1468        let zfar = 1000.0;
1469        let projection = glam::Mat4::perspective_rh(fovy, aspect, znear, zfar);
1470        let y = 50.0;
1471        let eye = Vec3::new(120.0, y, 120.0);
1472        let target = Vec3::new(0.0, y, 0.0);
1473        let up = Vec3::Y;
1474        let view = glam::Mat4::look_at_rh(eye, target, up);
1475
1476        let _camera = stage
1477            .new_camera()
1478            .with_projection_and_view(projection, view);
1479        let _doc = stage
1480            .load_gltf_document_from_path("../../gltf/Fox.glb")
1481            .unwrap();
1482
1483        // render a frame without vertex skinning as a baseline
1484        let frame = ctx.get_next_frame().unwrap();
1485        stage.render(&frame.view());
1486        let img = frame.read_image().block().unwrap();
1487        img_diff::assert_img_eq("gltf/skinning/rigged_fox_no_skinning.png", img);
1488
1489        // render a frame with vertex skinning to ensure our rigging is correct
1490        stage.set_has_vertex_skinning(true);
1491        let frame = ctx.get_next_frame().unwrap();
1492        stage.render(&frame.view());
1493        let img = frame.read_image().block().unwrap();
1494        img_diff::assert_img_eq_cfg(
1495            "gltf/skinning/rigged_fox_no_skinning.png",
1496            img,
1497            img_diff::DiffCfg {
1498                test_name: Some("gltf/skinning/rigged_fox"),
1499                ..Default::default()
1500            },
1501        );
1502
1503        // let mut animator = doc
1504        //     .animations
1505        //     .get(0)
1506        //     .unwrap()
1507        //     .clone()
1508        //     .into_animator(doc.nodes.iter().map(|n| (n.index, n.transform.clone())));
1509        // animator.progress(0.0).unwrap();
1510        // let frame = ctx.get_next_frame().unwrap();
1511        // stage.render(&frame.view());
1512        // let img = frame.read_image().unwrap();
1513        // img_diff::assert_img_eq_cfg(
1514        //     "gltf/skinning/rigged_fox_no_skinning.png",
1515        //     img,
1516        //     img_diff::DiffCfg {
1517        //         test_name: Some("gltf/skinning/rigged_fox_0"),
1518        //         ..Default::default()
1519        //     },
1520        // );
1521
1522        // let slab = futures_lite::future::block_on(stage.read(
1523        //     ctx.get_device(),
1524        //     ctx.get_queue(),
1525        //     Some("stage slab"),
1526        //     ..,
1527        // ))
1528        // .unwrap();
1529
1530        // assert_eq!(1, doc.skins.len());
1531        // let skin = doc.skins[0].skin.get();
1532        // for joint_index in 0..skin.joints.len() {
1533        //     // skin.get_joint_matrix(, , )
1534        // }
1535    }
1536
1537    #[test]
1538    fn camera_position_sanity() {
1539        // Test that the camera has the expected translation,
1540        // taking into account that the gltf files may have been
1541        // saved with Y up, or with Z up
1542        let ctx = Context::headless(100, 100).block();
1543        let stage = ctx.new_stage();
1544        let doc = stage
1545            .load_gltf_document_from_path(
1546                crate::test::workspace_dir()
1547                    .join("gltf")
1548                    .join("shadow_mapping_sanity_camera.gltf"),
1549            )
1550            .unwrap();
1551        let camera_a = doc.cameras.first().unwrap();
1552
1553        let desc = camera_a.camera.descriptor();
1554        const THRESHOLD: f32 = 10e-6;
1555        let a = Vec3::new(14.699949, 4.958309, 12.676651);
1556        let b = Vec3::new(14.699949, -12.676651, 4.958309);
1557        let distance_a = a.distance(desc.position());
1558        let distance_b = b.distance(desc.position());
1559        if distance_a > THRESHOLD && distance_b > THRESHOLD {
1560            println!("desc: {desc:#?}");
1561            println!("distance_a: {distance_a}");
1562            println!("distance_b: {distance_b}");
1563            println!("threshold: {THRESHOLD}");
1564            panic!("distance greater than threshold");
1565        }
1566
1567        let doc = stage
1568            .load_gltf_document_from_path(
1569                crate::test::workspace_dir()
1570                    .join("gltf")
1571                    .join("shadow_mapping_sanity.gltf"),
1572            )
1573            .unwrap();
1574        let camera_b = doc.cameras.first().unwrap();
1575
1576        let eq = |a: Vec3, b: Vec3| {
1577            let c = Vec3::new(b.x, -b.z, b.y);
1578            println!("a: {a}");
1579            println!("b: {b}");
1580            println!("c: {c}");
1581            a.distance(b) <= 10e-6 || c.distance(c) <= 10e-6
1582        };
1583        assert!(eq(
1584            camera_a.camera.descriptor().position(),
1585            camera_b.camera.descriptor().position()
1586        ));
1587    }
1588}