Skip to main content

renderling/
gltf.rs

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