bevy_gltf/loader/
mod.rs

1pub mod extensions;
2mod gltf_ext;
3
4use alloc::sync::Arc;
5use async_lock::RwLock;
6#[cfg(feature = "bevy_animation")]
7use bevy_animation::{prelude::*, AnimatedBy, AnimationTargetId};
8use bevy_asset::{
9    io::Reader, AssetLoadError, AssetLoader, AssetPath, Handle, LoadContext, ParseAssetPathError,
10    ReadAssetBytesError, RenderAssetUsages,
11};
12use bevy_camera::{
13    primitives::Aabb, visibility::Visibility, Camera, Camera3d, OrthographicProjection,
14    PerspectiveProjection, Projection, ScalingMode,
15};
16use bevy_color::{Color, LinearRgba};
17use bevy_ecs::{
18    entity::{Entity, EntityHashMap},
19    hierarchy::ChildSpawner,
20    name::Name,
21    world::World,
22};
23use bevy_image::{
24    CompressedImageFormats, Image, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor,
25    ImageType, TextureError,
26};
27use bevy_light::{DirectionalLight, PointLight, SpotLight};
28use bevy_math::{Mat4, Vec3};
29use bevy_mesh::{
30    morph::{MeshMorphWeights, MorphAttributes, MorphTargetImage, MorphWeights},
31    skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
32    Indices, Mesh, Mesh3d, MeshVertexAttribute, PrimitiveTopology,
33};
34#[cfg(feature = "pbr_transmission_textures")]
35use bevy_pbr::UvChannel;
36use bevy_pbr::{MeshMaterial3d, StandardMaterial, MAX_JOINTS};
37use bevy_platform::collections::{HashMap, HashSet};
38use bevy_reflect::TypePath;
39use bevy_render::render_resource::Face;
40use bevy_scene::Scene;
41#[cfg(not(target_arch = "wasm32"))]
42use bevy_tasks::IoTaskPool;
43use bevy_transform::components::Transform;
44use gltf::{
45    accessor::Iter,
46    image::Source,
47    mesh::{util::ReadIndices, Mode},
48    Material, Node, Semantic,
49};
50use serde::{Deserialize, Serialize};
51#[cfg(feature = "bevy_animation")]
52use smallvec::SmallVec;
53use std::{io::Error, sync::Mutex};
54use thiserror::Error;
55use tracing::{error, info_span, warn};
56
57use crate::{
58    convert_coordinates::ConvertCoordinates as _, vertex_attributes::convert_attribute, Gltf,
59    GltfAssetLabel, GltfExtras, GltfMaterialExtras, GltfMaterialName, GltfMeshExtras, GltfMeshName,
60    GltfNode, GltfSceneExtras, GltfSkin,
61};
62
63#[cfg(feature = "bevy_animation")]
64use self::gltf_ext::scene::collect_path;
65use self::{
66    extensions::{AnisotropyExtension, ClearcoatExtension, SpecularExtension},
67    gltf_ext::{
68        check_for_cycles, get_linear_textures,
69        material::{
70            alpha_mode, material_label, needs_tangents, uv_channel,
71            warn_on_differing_texture_transforms,
72        },
73        mesh::{primitive_name, primitive_topology},
74        scene::{node_name, node_transform},
75        texture::{texture_sampler, texture_transform_to_affine2},
76    },
77};
78use crate::convert_coordinates::GltfConvertCoordinates;
79
80/// An error that occurs when loading a glTF file.
81#[derive(Error, Debug)]
82pub enum GltfError {
83    /// Unsupported primitive mode.
84    #[error("unsupported primitive mode")]
85    UnsupportedPrimitive {
86        /// The primitive mode.
87        mode: Mode,
88    },
89    /// Invalid glTF file.
90    #[error("invalid glTF file: {0}")]
91    Gltf(#[from] gltf::Error),
92    /// Binary blob is missing.
93    #[error("binary blob is missing")]
94    MissingBlob,
95    /// Decoding the base64 mesh data failed.
96    #[error("failed to decode base64 mesh data")]
97    Base64Decode(#[from] base64::DecodeError),
98    /// Unsupported buffer format.
99    #[error("unsupported buffer format")]
100    BufferFormatUnsupported,
101    /// The buffer URI was unable to be resolved with respect to the asset path.
102    #[error("invalid buffer uri: {0}. asset path error={1}")]
103    InvalidBufferUri(String, ParseAssetPathError),
104    /// Invalid image mime type.
105    #[error("invalid image mime type: {0}")]
106    #[from(ignore)]
107    InvalidImageMimeType(String),
108    /// Error when loading a texture. Might be due to a disabled image file format feature.
109    #[error("You may need to add the feature for the file format: {0}")]
110    ImageError(#[from] TextureError),
111    /// The image URI was unable to be resolved with respect to the asset path.
112    #[error("invalid image uri: {0}. asset path error={1}")]
113    InvalidImageUri(String, ParseAssetPathError),
114    /// Failed to read bytes from an asset path.
115    #[error("failed to read bytes from an asset path: {0}")]
116    ReadAssetBytesError(#[from] ReadAssetBytesError),
117    /// Failed to load asset from an asset path.
118    #[error("failed to load asset from an asset path: {0}")]
119    AssetLoadError(#[from] AssetLoadError),
120    /// Missing sampler for an animation.
121    #[error("Missing sampler for animation {0}")]
122    #[from(ignore)]
123    MissingAnimationSampler(usize),
124    /// Failed to generate tangents.
125    #[error("failed to generate tangents: {0}")]
126    GenerateTangentsError(#[from] bevy_mesh::GenerateTangentsError),
127    /// Failed to generate morph targets.
128    #[error("failed to generate morph targets: {0}")]
129    MorphTarget(#[from] bevy_mesh::morph::MorphBuildError),
130    /// Circular children in Nodes
131    #[error("GLTF model must be a tree, found cycle instead at node indices: {0:?}")]
132    #[from(ignore)]
133    CircularChildren(String),
134    /// Failed to load a file.
135    #[error("failed to load file: {0}")]
136    Io(#[from] Error),
137}
138
139/// Loads glTF files with all of their data as their corresponding bevy representations.
140#[derive(TypePath)]
141pub struct GltfLoader {
142    /// List of compressed image formats handled by the loader.
143    pub supported_compressed_formats: CompressedImageFormats,
144    /// Custom vertex attributes that will be recognized when loading a glTF file.
145    ///
146    /// Keys must be the attribute names as found in the glTF data, which must start with an underscore.
147    /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
148    /// for additional details on custom attributes.
149    pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
150    /// Arc to default [`ImageSamplerDescriptor`].
151    pub default_sampler: Arc<Mutex<ImageSamplerDescriptor>>,
152    /// The default glTF coordinate conversion setting. This can be overridden
153    /// per-load by [`GltfLoaderSettings::convert_coordinates`].
154    pub default_convert_coordinates: GltfConvertCoordinates,
155    /// glTF extension data processors.
156    /// These are Bevy-side processors designed to access glTF
157    /// extension data during the loading process.
158    pub extensions: Arc<RwLock<Vec<Box<dyn extensions::GltfExtensionHandler>>>>,
159}
160
161/// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of
162/// the gltf will be loaded.
163///
164/// # Example
165///
166/// To load a gltf but exclude the cameras, replace a call to `asset_server.load("my.gltf")` with
167/// ```no_run
168/// # use bevy_asset::{AssetServer, Handle};
169/// # use bevy_gltf::*;
170/// # let asset_server: AssetServer = panic!();
171/// let gltf_handle: Handle<Gltf> = asset_server.load_with_settings(
172///     "my.gltf",
173///     |s: &mut GltfLoaderSettings| {
174///         s.load_cameras = false;
175///     }
176/// );
177/// ```
178#[derive(Serialize, Deserialize)]
179pub struct GltfLoaderSettings {
180    /// If empty, the gltf mesh nodes will be skipped.
181    ///
182    /// Otherwise, nodes will be loaded and retained in RAM/VRAM according to the active flags.
183    pub load_meshes: RenderAssetUsages,
184    /// If empty, the gltf materials will be skipped.
185    ///
186    /// Otherwise, materials will be loaded and retained in RAM/VRAM according to the active flags.
187    pub load_materials: RenderAssetUsages,
188    /// If true, the loader will spawn cameras for gltf camera nodes.
189    pub load_cameras: bool,
190    /// If true, the loader will spawn lights for gltf light nodes.
191    pub load_lights: bool,
192    /// If true, the loader will load `AnimationClip` assets, and also add
193    /// `AnimationTarget` and `AnimationPlayer` components to hierarchies
194    /// affected by animation. Requires the `bevy_animation` feature.
195    pub load_animations: bool,
196    /// If true, the loader will include the root of the gltf root node.
197    pub include_source: bool,
198    /// Overrides the default sampler. Data from sampler node is added on top of that.
199    ///
200    /// If None, uses the global default which is stored in the [`DefaultGltfImageSampler`](crate::DefaultGltfImageSampler) resource.
201    pub default_sampler: Option<ImageSamplerDescriptor>,
202    /// If true, the loader will ignore sampler data from gltf and use the default sampler.
203    pub override_sampler: bool,
204    /// Overrides the default glTF coordinate conversion setting.
205    ///
206    /// If `None`, uses the global default set by [`GltfPlugin::convert_coordinates`](crate::GltfPlugin::convert_coordinates).
207    pub convert_coordinates: Option<GltfConvertCoordinates>,
208}
209
210impl Default for GltfLoaderSettings {
211    fn default() -> Self {
212        Self {
213            load_meshes: RenderAssetUsages::default(),
214            load_materials: RenderAssetUsages::default(),
215            load_cameras: true,
216            load_lights: true,
217            load_animations: true,
218            include_source: false,
219            default_sampler: None,
220            override_sampler: false,
221            convert_coordinates: None,
222        }
223    }
224}
225
226impl GltfLoader {
227    /// Loads an entire glTF file.
228    pub async fn load_gltf<'a, 'b, 'c>(
229        loader: &GltfLoader,
230        bytes: &'a [u8],
231        load_context: &'b mut LoadContext<'c>,
232        settings: &'b GltfLoaderSettings,
233    ) -> Result<Gltf, GltfError> {
234        let gltf = gltf::Gltf::from_slice(bytes)?;
235
236        // clone extensions to start with a fresh processing state
237        let mut extensions = loader.extensions.read().await.clone();
238
239        // Extensions can have data on the "root" of the glTF data.
240        // Let extensions process the root data for the extension ids
241        // they've subscribed to.
242        for extension in extensions.iter_mut() {
243            extension.on_root(&gltf);
244        }
245
246        let file_name = load_context
247            .path()
248            .path()
249            .to_str()
250            .ok_or(GltfError::Gltf(gltf::Error::Io(Error::new(
251                std::io::ErrorKind::InvalidInput,
252                "Gltf file name invalid",
253            ))))?
254            .to_string();
255        let buffer_data = load_buffers(&gltf, load_context).await?;
256
257        let linear_textures = get_linear_textures(&gltf.document);
258
259        #[cfg(feature = "bevy_animation")]
260        let paths = if settings.load_animations {
261            let mut paths = HashMap::<usize, (usize, Vec<Name>)>::default();
262            for scene in gltf.scenes() {
263                for node in scene.nodes() {
264                    let root_index = node.index();
265                    collect_path(&node, &[], &mut paths, root_index, &mut HashSet::default());
266                }
267            }
268            paths
269        } else {
270            Default::default()
271        };
272
273        let convert_coordinates = match settings.convert_coordinates {
274            Some(convert_coordinates) => convert_coordinates,
275            None => loader.default_convert_coordinates,
276        };
277
278        #[cfg(feature = "bevy_animation")]
279        let (animations, named_animations, animation_roots) = if settings.load_animations {
280            use bevy_animation::{
281                animated_field, animation_curves::*, gltf_curves::*, VariableCurve,
282            };
283            use bevy_math::{
284                curve::{ConstantCurve, Interval, UnevenSampleAutoCurve},
285                Quat, Vec4,
286            };
287            use gltf::animation::util::ReadOutputs;
288            let mut animations = vec![];
289            let mut named_animations = <HashMap<_, _>>::default();
290            let mut animation_roots = <HashSet<_>>::default();
291            for animation in gltf.animations() {
292                let mut animation_clip = AnimationClip::default();
293                for channel in animation.channels() {
294                    let node = channel.target().node();
295                    let interpolation = channel.sampler().interpolation();
296                    let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
297                    let keyframe_timestamps: Vec<f32> = if let Some(inputs) = reader.read_inputs() {
298                        match inputs {
299                            Iter::Standard(times) => times.collect(),
300                            Iter::Sparse(_) => {
301                                warn!("Sparse accessor not supported for animation sampler input");
302                                continue;
303                            }
304                        }
305                    } else {
306                        warn!("Animations without a sampler input are not supported");
307                        return Err(GltfError::MissingAnimationSampler(animation.index()));
308                    };
309
310                    if keyframe_timestamps.is_empty() {
311                        warn!("Tried to load animation with no keyframe timestamps");
312                        continue;
313                    }
314
315                    let maybe_curve: Option<VariableCurve> = if let Some(outputs) =
316                        reader.read_outputs()
317                    {
318                        match outputs {
319                            ReadOutputs::Translations(tr) => {
320                                let translation_property = animated_field!(Transform::translation);
321                                let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
322                                if keyframe_timestamps.len() == 1 {
323                                    Some(VariableCurve::new(AnimatableCurve::new(
324                                        translation_property,
325                                        ConstantCurve::new(Interval::EVERYWHERE, translations[0]),
326                                    )))
327                                } else {
328                                    match interpolation {
329                                        gltf::animation::Interpolation::Linear => {
330                                            UnevenSampleAutoCurve::new(
331                                                keyframe_timestamps.into_iter().zip(translations),
332                                            )
333                                            .ok()
334                                            .map(
335                                                |curve| {
336                                                    VariableCurve::new(AnimatableCurve::new(
337                                                        translation_property,
338                                                        curve,
339                                                    ))
340                                                },
341                                            )
342                                        }
343                                        gltf::animation::Interpolation::Step => {
344                                            SteppedKeyframeCurve::new(
345                                                keyframe_timestamps.into_iter().zip(translations),
346                                            )
347                                            .ok()
348                                            .map(
349                                                |curve| {
350                                                    VariableCurve::new(AnimatableCurve::new(
351                                                        translation_property,
352                                                        curve,
353                                                    ))
354                                                },
355                                            )
356                                        }
357                                        gltf::animation::Interpolation::CubicSpline => {
358                                            CubicKeyframeCurve::new(
359                                                keyframe_timestamps,
360                                                translations,
361                                            )
362                                            .ok()
363                                            .map(
364                                                |curve| {
365                                                    VariableCurve::new(AnimatableCurve::new(
366                                                        translation_property,
367                                                        curve,
368                                                    ))
369                                                },
370                                            )
371                                        }
372                                    }
373                                }
374                            }
375                            ReadOutputs::Rotations(rots) => {
376                                let rotation_property = animated_field!(Transform::rotation);
377                                let rotations: Vec<Quat> =
378                                    rots.into_f32().map(Quat::from_array).collect();
379                                if keyframe_timestamps.len() == 1 {
380                                    Some(VariableCurve::new(AnimatableCurve::new(
381                                        rotation_property,
382                                        ConstantCurve::new(Interval::EVERYWHERE, rotations[0]),
383                                    )))
384                                } else {
385                                    match interpolation {
386                                        gltf::animation::Interpolation::Linear => {
387                                            UnevenSampleAutoCurve::new(
388                                                keyframe_timestamps.into_iter().zip(rotations),
389                                            )
390                                            .ok()
391                                            .map(
392                                                |curve| {
393                                                    VariableCurve::new(AnimatableCurve::new(
394                                                        rotation_property,
395                                                        curve,
396                                                    ))
397                                                },
398                                            )
399                                        }
400                                        gltf::animation::Interpolation::Step => {
401                                            SteppedKeyframeCurve::new(
402                                                keyframe_timestamps.into_iter().zip(rotations),
403                                            )
404                                            .ok()
405                                            .map(
406                                                |curve| {
407                                                    VariableCurve::new(AnimatableCurve::new(
408                                                        rotation_property,
409                                                        curve,
410                                                    ))
411                                                },
412                                            )
413                                        }
414                                        gltf::animation::Interpolation::CubicSpline => {
415                                            CubicRotationCurve::new(
416                                                keyframe_timestamps,
417                                                rotations.into_iter().map(Vec4::from),
418                                            )
419                                            .ok()
420                                            .map(
421                                                |curve| {
422                                                    VariableCurve::new(AnimatableCurve::new(
423                                                        rotation_property,
424                                                        curve,
425                                                    ))
426                                                },
427                                            )
428                                        }
429                                    }
430                                }
431                            }
432                            ReadOutputs::Scales(scale) => {
433                                let scale_property = animated_field!(Transform::scale);
434                                let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
435                                if keyframe_timestamps.len() == 1 {
436                                    Some(VariableCurve::new(AnimatableCurve::new(
437                                        scale_property,
438                                        ConstantCurve::new(Interval::EVERYWHERE, scales[0]),
439                                    )))
440                                } else {
441                                    match interpolation {
442                                        gltf::animation::Interpolation::Linear => {
443                                            UnevenSampleAutoCurve::new(
444                                                keyframe_timestamps.into_iter().zip(scales),
445                                            )
446                                            .ok()
447                                            .map(
448                                                |curve| {
449                                                    VariableCurve::new(AnimatableCurve::new(
450                                                        scale_property,
451                                                        curve,
452                                                    ))
453                                                },
454                                            )
455                                        }
456                                        gltf::animation::Interpolation::Step => {
457                                            SteppedKeyframeCurve::new(
458                                                keyframe_timestamps.into_iter().zip(scales),
459                                            )
460                                            .ok()
461                                            .map(
462                                                |curve| {
463                                                    VariableCurve::new(AnimatableCurve::new(
464                                                        scale_property,
465                                                        curve,
466                                                    ))
467                                                },
468                                            )
469                                        }
470                                        gltf::animation::Interpolation::CubicSpline => {
471                                            CubicKeyframeCurve::new(keyframe_timestamps, scales)
472                                                .ok()
473                                                .map(|curve| {
474                                                    VariableCurve::new(AnimatableCurve::new(
475                                                        scale_property,
476                                                        curve,
477                                                    ))
478                                                })
479                                        }
480                                    }
481                                }
482                            }
483                            ReadOutputs::MorphTargetWeights(weights) => {
484                                let weights: Vec<f32> = weights.into_f32().collect();
485                                if keyframe_timestamps.len() == 1 {
486                                    #[expect(
487                                        clippy::unnecessary_map_on_constructor,
488                                        reason = "While the mapping is unnecessary, it is much more readable at this level of indentation. Additionally, mapping makes it more consistent with the other branches."
489                                    )]
490                                    Some(ConstantCurve::new(Interval::EVERYWHERE, weights))
491                                        .map(WeightsCurve)
492                                        .map(VariableCurve::new)
493                                } else {
494                                    match interpolation {
495                                        gltf::animation::Interpolation::Linear => {
496                                            WideLinearKeyframeCurve::new(
497                                                keyframe_timestamps,
498                                                weights,
499                                            )
500                                            .ok()
501                                            .map(WeightsCurve)
502                                            .map(VariableCurve::new)
503                                        }
504                                        gltf::animation::Interpolation::Step => {
505                                            WideSteppedKeyframeCurve::new(
506                                                keyframe_timestamps,
507                                                weights,
508                                            )
509                                            .ok()
510                                            .map(WeightsCurve)
511                                            .map(VariableCurve::new)
512                                        }
513                                        gltf::animation::Interpolation::CubicSpline => {
514                                            WideCubicKeyframeCurve::new(
515                                                keyframe_timestamps,
516                                                weights,
517                                            )
518                                            .ok()
519                                            .map(WeightsCurve)
520                                            .map(VariableCurve::new)
521                                        }
522                                    }
523                                }
524                            }
525                        }
526                    } else {
527                        warn!("Animations without a sampler output are not supported");
528                        return Err(GltfError::MissingAnimationSampler(animation.index()));
529                    };
530
531                    let Some(curve) = maybe_curve else {
532                        warn!(
533                            "Invalid keyframe data for node {}; curve could not be constructed",
534                            node.index()
535                        );
536                        continue;
537                    };
538
539                    if let Some((root_index, path)) = paths.get(&node.index()) {
540                        animation_roots.insert(*root_index);
541                        animation_clip.add_variable_curve_to_target(
542                            AnimationTargetId::from_names(path.iter()),
543                            curve,
544                        );
545                    } else {
546                        warn!(
547                        "Animation ignored for node {}: part of its hierarchy is missing a name",
548                        node.index()
549                    );
550                    }
551                }
552                let handle = load_context.add_labeled_asset(
553                    GltfAssetLabel::Animation(animation.index()).to_string(),
554                    animation_clip,
555                );
556                if let Some(name) = animation.name() {
557                    named_animations.insert(name.into(), handle.clone());
558                }
559
560                // let extensions handle extension data placed on animations
561                for extension in extensions.iter_mut() {
562                    extension.on_animation(&animation, handle.clone());
563                }
564
565                animations.push(handle);
566            }
567
568            // let extensions process the collection of animation data
569            // this only happens once for each GltfExtensionHandler because
570            // it is a hook for Bevy's finalized representation of the animations
571            for extension in extensions.iter_mut() {
572                extension.on_animations_collected(
573                    load_context,
574                    &animations,
575                    &named_animations,
576                    &animation_roots,
577                );
578            }
579
580            (animations, named_animations, animation_roots)
581        } else {
582            Default::default()
583        };
584
585        let default_sampler = match settings.default_sampler.as_ref() {
586            Some(sampler) => sampler,
587            None => &loader.default_sampler.lock().unwrap().clone(),
588        };
589        // We collect handles to ensure loaded images from paths are not unloaded before they are used elsewhere
590        // in the loader. This prevents "reloads", but it also prevents dropping the is_srgb context on reload.
591        //
592        // In theory we could store a mapping between texture.index() and handle to use
593        // later in the loader when looking up handles for materials. However this would mean
594        // that the material's load context would no longer track those images as dependencies.
595        let mut texture_handles = Vec::new();
596        if gltf.textures().len() == 1 || cfg!(target_arch = "wasm32") {
597            for texture in gltf.textures() {
598                let image = load_image(
599                    texture.clone(),
600                    &buffer_data,
601                    &linear_textures,
602                    load_context.path(),
603                    loader.supported_compressed_formats,
604                    default_sampler,
605                    settings,
606                )
607                .await?;
608                image.process_loaded_texture(load_context, &mut texture_handles);
609                // let extensions handle texture data
610                for extension in extensions.iter_mut() {
611                    extension.on_texture(&texture, texture_handles.last().unwrap().clone());
612                }
613            }
614        } else {
615            #[cfg(not(target_arch = "wasm32"))]
616            IoTaskPool::get()
617                .scope(|scope| {
618                    gltf.textures().for_each(|gltf_texture| {
619                        let asset_path = load_context.path().clone();
620                        let linear_textures = &linear_textures;
621                        let buffer_data = &buffer_data;
622                        scope.spawn(async move {
623                            load_image(
624                                gltf_texture,
625                                buffer_data,
626                                linear_textures,
627                                &asset_path,
628                                loader.supported_compressed_formats,
629                                default_sampler,
630                                settings,
631                            )
632                            .await
633                        });
634                    });
635                })
636                .into_iter()
637                // order is preserved if the futures are only spawned from the root scope
638                .zip(gltf.textures())
639                .for_each(|(result, texture)| match result {
640                    Ok(image) => {
641                        image.process_loaded_texture(load_context, &mut texture_handles);
642                        // let extensions handle texture data
643                        for extension in extensions.iter_mut() {
644                            extension.on_texture(&texture, texture_handles.last().unwrap().clone());
645                        }
646                    }
647                    Err(err) => {
648                        warn!("Error loading glTF texture: {}", err);
649                    }
650                });
651        }
652
653        let mut materials = vec![];
654        let mut named_materials = <HashMap<_, _>>::default();
655        // Only include materials in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_materials flag
656        if !settings.load_materials.is_empty() {
657            // NOTE: materials must be loaded after textures because image load() calls will happen before load_with_settings, preventing is_srgb from being set properly
658            for material in gltf.materials() {
659                let handle = {
660                    let (label, material) = load_material(
661                        &material,
662                        &texture_handles,
663                        false,
664                        load_context.path().clone(),
665                    );
666                    load_context.add_labeled_asset(label, material)
667                };
668                if let Some(name) = material.name() {
669                    named_materials.insert(name.into(), handle.clone());
670                }
671
672                // let extensions handle material data
673                for extension in extensions.iter_mut() {
674                    extension.on_material(load_context, &material, handle.clone());
675                }
676
677                materials.push(handle);
678            }
679        }
680        let mut meshes = vec![];
681        let mut named_meshes = <HashMap<_, _>>::default();
682        let mut meshes_on_skinned_nodes = <HashSet<_>>::default();
683        let mut meshes_on_non_skinned_nodes = <HashSet<_>>::default();
684        for gltf_node in gltf.nodes() {
685            if gltf_node.skin().is_some() {
686                if let Some(mesh) = gltf_node.mesh() {
687                    meshes_on_skinned_nodes.insert(mesh.index());
688                }
689            } else if let Some(mesh) = gltf_node.mesh() {
690                meshes_on_non_skinned_nodes.insert(mesh.index());
691            }
692        }
693        for gltf_mesh in gltf.meshes() {
694            let mut primitives = vec![];
695            for primitive in gltf_mesh.primitives() {
696                let primitive_label = GltfAssetLabel::Primitive {
697                    mesh: gltf_mesh.index(),
698                    primitive: primitive.index(),
699                };
700                let primitive_topology = primitive_topology(primitive.mode())?;
701
702                let mut mesh = Mesh::new(primitive_topology, settings.load_meshes);
703
704                // Read vertex attributes
705                for (semantic, accessor) in primitive.attributes() {
706                    if [Semantic::Joints(0), Semantic::Weights(0)].contains(&semantic) {
707                        if !meshes_on_skinned_nodes.contains(&gltf_mesh.index()) {
708                            warn!(
709                        "Ignoring attribute {:?} for skinned mesh {} used on non skinned nodes (NODE_SKINNED_MESH_WITHOUT_SKIN)",
710                        semantic,
711                        primitive_label
712                    );
713                            continue;
714                        } else if meshes_on_non_skinned_nodes.contains(&gltf_mesh.index()) {
715                            error!("Skinned mesh {} used on both skinned and non skin nodes, this is likely to cause an error (NODE_SKINNED_MESH_WITHOUT_SKIN)", primitive_label);
716                        }
717                    }
718                    match convert_attribute(
719                        semantic,
720                        accessor,
721                        &buffer_data,
722                        &loader.custom_vertex_attributes,
723                        convert_coordinates.rotate_meshes,
724                    ) {
725                        Ok((attribute, values)) => mesh.insert_attribute(attribute, values),
726                        Err(err) => warn!("{}", err),
727                    }
728                }
729
730                // Read vertex indices
731                let reader =
732                    primitive.reader(|buffer| Some(buffer_data[buffer.index()].as_slice()));
733                if let Some(indices) = reader.read_indices() {
734                    mesh.insert_indices(match indices {
735                        ReadIndices::U8(is) => Indices::U16(is.map(|x| x as u16).collect()),
736                        ReadIndices::U16(is) => Indices::U16(is.collect()),
737                        ReadIndices::U32(is) => Indices::U32(is.collect()),
738                    });
739                };
740
741                {
742                    let morph_target_reader = reader.read_morph_targets();
743                    if morph_target_reader.len() != 0 {
744                        let morph_targets_label = GltfAssetLabel::MorphTarget {
745                            mesh: gltf_mesh.index(),
746                            primitive: primitive.index(),
747                        };
748                        let morph_target_image = MorphTargetImage::new(
749                            morph_target_reader.map(|i| PrimitiveMorphAttributesIter {
750                                convert_coordinates: convert_coordinates.rotate_meshes,
751                                positions: i.0,
752                                normals: i.1,
753                                tangents: i.2,
754                            }),
755                            mesh.count_vertices(),
756                            RenderAssetUsages::default(),
757                        )?;
758                        let handle = load_context.add_labeled_asset(
759                            morph_targets_label.to_string(),
760                            morph_target_image.0,
761                        );
762
763                        mesh.set_morph_targets(handle);
764                        let extras = gltf_mesh.extras().as_ref();
765                        if let Some(names) = extras.and_then(|extras| {
766                            serde_json::from_str::<MorphTargetNames>(extras.get()).ok()
767                        }) {
768                            mesh.set_morph_target_names(names.target_names);
769                        }
770                    }
771                }
772
773                if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_none()
774                    && matches!(mesh.primitive_topology(), PrimitiveTopology::TriangleList)
775                {
776                    tracing::debug!(
777                        "Automatically calculating missing vertex normals for geometry."
778                    );
779                    let vertex_count_before = mesh.count_vertices();
780                    mesh.duplicate_vertices();
781                    mesh.compute_flat_normals();
782                    let vertex_count_after = mesh.count_vertices();
783                    if vertex_count_before != vertex_count_after {
784                        tracing::debug!("Missing vertex normals in indexed geometry, computing them as flat. Vertex count increased from {} to {}", vertex_count_before, vertex_count_after);
785                    } else {
786                        tracing::debug!(
787                            "Missing vertex normals in indexed geometry, computing them as flat."
788                        );
789                    }
790                }
791
792                if !mesh.contains_attribute(Mesh::ATTRIBUTE_TANGENT)
793                    && mesh.contains_attribute(Mesh::ATTRIBUTE_NORMAL)
794                    && needs_tangents(&primitive.material())
795                {
796                    tracing::debug!(
797                        "Missing vertex tangents for {}, computing them using the mikktspace algorithm. Consider using a tool such as Blender to pre-compute the tangents.", file_name
798                    );
799
800                    let generate_tangents_span = info_span!("generate_tangents", name = file_name);
801
802                    generate_tangents_span.in_scope(|| {
803                        if let Err(err) = mesh.generate_tangents() {
804                            warn!(
805                                "Failed to generate vertex tangents using the mikktspace algorithm: {}",
806                                err
807                            );
808                        }
809                    });
810                }
811
812                let mesh_handle = load_context.add_labeled_asset(primitive_label.to_string(), mesh);
813                primitives.push(super::GltfPrimitive::new(
814                    &gltf_mesh,
815                    &primitive,
816                    mesh_handle,
817                    primitive
818                        .material()
819                        .index()
820                        .and_then(|i| materials.get(i).cloned()),
821                    primitive.extras().as_deref().map(GltfExtras::from),
822                    primitive
823                        .material()
824                        .extras()
825                        .as_deref()
826                        .map(GltfExtras::from),
827                ));
828            }
829
830            let mesh = super::GltfMesh::new(
831                &gltf_mesh,
832                primitives,
833                gltf_mesh.extras().as_deref().map(GltfExtras::from),
834            );
835
836            let handle = load_context.add_labeled_asset(mesh.asset_label().to_string(), mesh);
837            if let Some(name) = gltf_mesh.name() {
838                named_meshes.insert(name.into(), handle.clone());
839            }
840            for extension in extensions.iter_mut() {
841                extension.on_gltf_mesh(load_context, &gltf_mesh, handle.clone());
842            }
843
844            meshes.push(handle);
845        }
846
847        let skinned_mesh_inverse_bindposes: Vec<_> = gltf
848            .skins()
849            .map(|gltf_skin| {
850                let reader = gltf_skin.reader(|buffer| Some(&buffer_data[buffer.index()]));
851                let local_to_bone_bind_matrices: Vec<Mat4> = reader
852                    .read_inverse_bind_matrices()
853                    .map(|mats| {
854                        mats.map(|mat| {
855                            Mat4::from_cols_array_2d(&mat)
856                                * convert_coordinates.mesh_conversion_mat4()
857                        })
858                        .collect()
859                    })
860                    .unwrap_or_else(|| {
861                        core::iter::repeat_n(Mat4::IDENTITY, gltf_skin.joints().len()).collect()
862                    });
863
864                load_context.add_labeled_asset(
865                    GltfAssetLabel::InverseBindMatrices(gltf_skin.index()).to_string(),
866                    SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices),
867                )
868            })
869            .collect();
870
871        let mut nodes = HashMap::<usize, Handle<GltfNode>>::default();
872        let mut named_nodes = <HashMap<_, _>>::default();
873        let mut skins = <HashMap<_, _>>::default();
874        let mut named_skins = <HashMap<_, _>>::default();
875
876        // First, create the node handles.
877        for node in gltf.nodes() {
878            let label = GltfAssetLabel::Node(node.index());
879            let label_handle = load_context.get_label_handle(label.to_string());
880            nodes.insert(node.index(), label_handle);
881        }
882
883        // Then check for cycles.
884        check_for_cycles(&gltf)?;
885
886        // Now populate the nodes.
887        for node in gltf.nodes() {
888            let skin = node.skin().map(|skin| {
889                skins
890                    .entry(skin.index())
891                    .or_insert_with(|| {
892                        let joints: Vec<_> = skin
893                            .joints()
894                            .map(|joint| nodes.get(&joint.index()).unwrap().clone())
895                            .collect();
896
897                        if joints.len() > MAX_JOINTS {
898                            warn!(
899                                "The glTF skin {} has {} joints, but the maximum supported is {}",
900                                skin.name()
901                                    .map(ToString::to_string)
902                                    .unwrap_or_else(|| skin.index().to_string()),
903                                joints.len(),
904                                MAX_JOINTS
905                            );
906                        }
907
908                        let gltf_skin = GltfSkin::new(
909                            &skin,
910                            joints,
911                            skinned_mesh_inverse_bindposes[skin.index()].clone(),
912                            skin.extras().as_deref().map(GltfExtras::from),
913                        );
914
915                        let handle = load_context
916                            .add_labeled_asset(gltf_skin.asset_label().to_string(), gltf_skin);
917
918                        if let Some(name) = skin.name() {
919                            named_skins.insert(name.into(), handle.clone());
920                        }
921
922                        handle
923                    })
924                    .clone()
925            });
926
927            let children = node
928                .children()
929                .map(|child| nodes.get(&child.index()).unwrap().clone())
930                .collect();
931
932            let mesh = node
933                .mesh()
934                .map(|mesh| mesh.index())
935                .and_then(|i| meshes.get(i).cloned());
936
937            let gltf_node = GltfNode::new(
938                &node,
939                children,
940                mesh,
941                node_transform(&node),
942                skin,
943                node.extras().as_deref().map(GltfExtras::from),
944            );
945
946            #[cfg(feature = "bevy_animation")]
947            let gltf_node = gltf_node.with_animation_root(animation_roots.contains(&node.index()));
948
949            let handle =
950                load_context.add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node);
951            nodes.insert(node.index(), handle.clone());
952            if let Some(name) = node.name() {
953                named_nodes.insert(name.into(), handle);
954            }
955        }
956
957        let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
958        nodes_to_sort.sort_by_key(|(i, _)| *i);
959        let nodes = nodes_to_sort
960            .into_iter()
961            .map(|(_, resolved)| resolved)
962            .collect();
963
964        let mut scenes = vec![];
965        let mut named_scenes = <HashMap<_, _>>::default();
966        let mut active_camera_found = false;
967        for scene in gltf.scenes() {
968            let mut err = None;
969            let mut world = World::default();
970            let mut node_index_to_entity_map = <HashMap<_, _>>::default();
971            let mut entity_to_skin_index_map = EntityHashMap::default();
972            let mut scene_load_context = load_context.begin_labeled_asset();
973
974            let world_root_transform = convert_coordinates.scene_conversion_transform();
975
976            let world_root_id = world
977                .spawn((world_root_transform, Visibility::default()))
978                .with_children(|parent| {
979                    for node in scene.nodes() {
980                        let result = load_node(
981                            &node,
982                            parent,
983                            load_context,
984                            &mut scene_load_context,
985                            settings,
986                            &mut node_index_to_entity_map,
987                            &mut entity_to_skin_index_map,
988                            &mut active_camera_found,
989                            &Transform::default(),
990                            #[cfg(feature = "bevy_animation")]
991                            &animation_roots,
992                            #[cfg(feature = "bevy_animation")]
993                            None,
994                            &texture_handles,
995                            &convert_coordinates,
996                            &mut extensions,
997                        );
998                        if result.is_err() {
999                            err = Some(result);
1000                            return;
1001                        }
1002                    }
1003                })
1004                .id();
1005
1006            if let Some(extras) = scene.extras().as_ref() {
1007                world.entity_mut(world_root_id).insert(GltfSceneExtras {
1008                    value: extras.get().to_string(),
1009                });
1010            }
1011
1012            if let Some(Err(err)) = err {
1013                return Err(err);
1014            }
1015
1016            #[cfg(feature = "bevy_animation")]
1017            {
1018                // for each node root in a scene, check if it's the root of an animation
1019                // if it is, add the AnimationPlayer component
1020                for node in scene.nodes() {
1021                    if animation_roots.contains(&node.index()) {
1022                        world
1023                            .entity_mut(*node_index_to_entity_map.get(&node.index()).unwrap())
1024                            .insert(AnimationPlayer::default());
1025                    }
1026                }
1027            }
1028
1029            for (&entity, &skin_index) in &entity_to_skin_index_map {
1030                let mut entity = world.entity_mut(entity);
1031                let skin = gltf.skins().nth(skin_index).unwrap();
1032                let joint_entities: Vec<_> = skin
1033                    .joints()
1034                    .map(|node| node_index_to_entity_map[&node.index()])
1035                    .collect();
1036
1037                entity.insert(SkinnedMesh {
1038                    inverse_bindposes: skinned_mesh_inverse_bindposes[skin_index].clone(),
1039                    joints: joint_entities,
1040                });
1041            }
1042
1043            // let extensions handle scene extension data
1044            for extension in extensions.iter_mut() {
1045                extension.on_scene_completed(
1046                    &mut scene_load_context,
1047                    &scene,
1048                    world_root_id,
1049                    &mut world,
1050                );
1051            }
1052
1053            let loaded_scene = scene_load_context.finish(Scene::new(world));
1054            let scene_handle = load_context.add_loaded_labeled_asset(
1055                GltfAssetLabel::Scene(scene.index()).to_string(),
1056                loaded_scene,
1057            );
1058
1059            if let Some(name) = scene.name() {
1060                named_scenes.insert(name.into(), scene_handle.clone());
1061            }
1062            scenes.push(scene_handle);
1063        }
1064
1065        Ok(Gltf {
1066            default_scene: gltf
1067                .default_scene()
1068                .and_then(|scene| scenes.get(scene.index()))
1069                .cloned(),
1070            scenes,
1071            named_scenes,
1072            meshes,
1073            named_meshes,
1074            skins: skins.into_values().collect(),
1075            named_skins,
1076            materials,
1077            named_materials,
1078            nodes,
1079            named_nodes,
1080            #[cfg(feature = "bevy_animation")]
1081            animations,
1082            #[cfg(feature = "bevy_animation")]
1083            named_animations,
1084            source: if settings.include_source {
1085                Some(gltf)
1086            } else {
1087                None
1088            },
1089        })
1090    }
1091}
1092
1093impl AssetLoader for GltfLoader {
1094    type Asset = Gltf;
1095    type Settings = GltfLoaderSettings;
1096    type Error = GltfError;
1097    async fn load(
1098        &self,
1099        reader: &mut dyn Reader,
1100        settings: &GltfLoaderSettings,
1101        load_context: &mut LoadContext<'_>,
1102    ) -> Result<Gltf, Self::Error> {
1103        let mut bytes = Vec::new();
1104        reader.read_to_end(&mut bytes).await?;
1105
1106        Self::load_gltf(self, &bytes, load_context, settings).await
1107    }
1108
1109    fn extensions(&self) -> &[&str] {
1110        &["gltf", "glb"]
1111    }
1112}
1113
1114/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label.
1115async fn load_image<'a, 'b>(
1116    gltf_texture: gltf::Texture<'a>,
1117    buffer_data: &[Vec<u8>],
1118    linear_textures: &HashSet<usize>,
1119    gltf_path: &'b AssetPath<'b>,
1120    supported_compressed_formats: CompressedImageFormats,
1121    default_sampler: &ImageSamplerDescriptor,
1122    settings: &GltfLoaderSettings,
1123) -> Result<ImageOrPath, GltfError> {
1124    let is_srgb = !linear_textures.contains(&gltf_texture.index());
1125    let sampler_descriptor = if settings.override_sampler {
1126        default_sampler.clone()
1127    } else {
1128        texture_sampler(&gltf_texture, default_sampler)
1129    };
1130
1131    match gltf_texture.source().source() {
1132        Source::View { view, mime_type } => {
1133            let start = view.offset();
1134            let end = view.offset() + view.length();
1135            let buffer = &buffer_data[view.buffer().index()][start..end];
1136            let image = Image::from_buffer(
1137                buffer,
1138                ImageType::MimeType(mime_type),
1139                supported_compressed_formats,
1140                is_srgb,
1141                ImageSampler::Descriptor(sampler_descriptor),
1142                settings.load_materials,
1143            )?;
1144            Ok(ImageOrPath::Image {
1145                image,
1146                label: GltfAssetLabel::Texture(gltf_texture.index()),
1147            })
1148        }
1149        Source::Uri { uri, mime_type } => {
1150            let uri = percent_encoding::percent_decode_str(uri)
1151                .decode_utf8()
1152                .unwrap();
1153            let uri = uri.as_ref();
1154            if let Ok(data_uri) = DataUri::parse(uri) {
1155                let bytes = data_uri.decode()?;
1156                let image_type = ImageType::MimeType(data_uri.mime_type);
1157                Ok(ImageOrPath::Image {
1158                    image: Image::from_buffer(
1159                        &bytes,
1160                        mime_type.map(ImageType::MimeType).unwrap_or(image_type),
1161                        supported_compressed_formats,
1162                        is_srgb,
1163                        ImageSampler::Descriptor(sampler_descriptor),
1164                        settings.load_materials,
1165                    )?,
1166                    label: GltfAssetLabel::Texture(gltf_texture.index()),
1167                })
1168            } else {
1169                let image_path = gltf_path
1170                    .resolve_embed(uri)
1171                    .map_err(|err| GltfError::InvalidImageUri(uri.to_owned(), err))?;
1172                Ok(ImageOrPath::Path {
1173                    path: image_path,
1174                    is_srgb,
1175                    sampler_descriptor,
1176                    render_asset_usages: settings.load_materials,
1177                })
1178            }
1179        }
1180    }
1181}
1182
1183/// Loads a glTF material as a bevy [`StandardMaterial`] and returns the label and material.
1184// Note: this function intentionally **does not** take a `LoadContext` and insert the asset here,
1185// since we don't use the `LoadContext` otherwise, and this prevents accidentally using the context
1186// without `labeled_asset_scope`.
1187fn load_material(
1188    material: &Material,
1189    textures: &[Handle<Image>],
1190    is_scale_inverted: bool,
1191    asset_path: AssetPath<'_>,
1192) -> (String, StandardMaterial) {
1193    let pbr = material.pbr_metallic_roughness();
1194
1195    // TODO: handle missing label handle errors here?
1196    let color = pbr.base_color_factor();
1197    let base_color_channel = pbr
1198        .base_color_texture()
1199        .map(|info| uv_channel(material, "base color", info.tex_coord()))
1200        .unwrap_or_default();
1201    let base_color_texture = pbr.base_color_texture().map(|info| {
1202        textures
1203            .get(info.texture().index())
1204            .cloned()
1205            .unwrap_or_default()
1206    });
1207
1208    let uv_transform = pbr
1209        .base_color_texture()
1210        .and_then(|info| info.texture_transform().map(texture_transform_to_affine2))
1211        .unwrap_or_default();
1212
1213    let normal_map_channel = material
1214        .normal_texture()
1215        .map(|info| uv_channel(material, "normal map", info.tex_coord()))
1216        .unwrap_or_default();
1217    let normal_map_texture: Option<Handle<Image>> =
1218        material.normal_texture().map(|normal_texture| {
1219            // TODO: handle normal_texture.scale
1220            textures
1221                .get(normal_texture.texture().index())
1222                .cloned()
1223                .unwrap_or_default()
1224        });
1225
1226    let metallic_roughness_channel = pbr
1227        .metallic_roughness_texture()
1228        .map(|info| uv_channel(material, "metallic/roughness", info.tex_coord()))
1229        .unwrap_or_default();
1230    let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| {
1231        warn_on_differing_texture_transforms(material, &info, uv_transform, "metallic/roughness");
1232        textures
1233            .get(info.texture().index())
1234            .cloned()
1235            .unwrap_or_default()
1236    });
1237
1238    let occlusion_channel = material
1239        .occlusion_texture()
1240        .map(|info| uv_channel(material, "occlusion", info.tex_coord()))
1241        .unwrap_or_default();
1242    let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| {
1243        // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
1244        textures
1245            .get(occlusion_texture.texture().index())
1246            .cloned()
1247            .unwrap_or_default()
1248    });
1249
1250    let emissive = material.emissive_factor();
1251    let emissive_channel = material
1252        .emissive_texture()
1253        .map(|info| uv_channel(material, "emissive", info.tex_coord()))
1254        .unwrap_or_default();
1255    let emissive_texture = material.emissive_texture().map(|info| {
1256        // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
1257        warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive");
1258        textures
1259            .get(info.texture().index())
1260            .cloned()
1261            .unwrap_or_default()
1262    });
1263
1264    #[cfg(feature = "pbr_transmission_textures")]
1265    let (specular_transmission, specular_transmission_channel, specular_transmission_texture) =
1266        material
1267            .transmission()
1268            .map_or((0.0, UvChannel::Uv0, None), |transmission| {
1269                let specular_transmission_channel = transmission
1270                    .transmission_texture()
1271                    .map(|info| uv_channel(material, "specular/transmission", info.tex_coord()))
1272                    .unwrap_or_default();
1273                let transmission_texture: Option<Handle<Image>> = transmission
1274                    .transmission_texture()
1275                    .map(|transmission_texture| {
1276                        textures
1277                            .get(transmission_texture.texture().index())
1278                            .cloned()
1279                            .unwrap_or_default()
1280                    });
1281
1282                (
1283                    transmission.transmission_factor(),
1284                    specular_transmission_channel,
1285                    transmission_texture,
1286                )
1287            });
1288
1289    #[cfg(not(feature = "pbr_transmission_textures"))]
1290    let specular_transmission = material
1291        .transmission()
1292        .map_or(0.0, |transmission| transmission.transmission_factor());
1293
1294    #[cfg(feature = "pbr_transmission_textures")]
1295    let (thickness, thickness_channel, thickness_texture, attenuation_distance, attenuation_color) =
1296        material.volume().map_or(
1297            (0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]),
1298            |volume| {
1299                let thickness_channel = volume
1300                    .thickness_texture()
1301                    .map(|info| uv_channel(material, "thickness", info.tex_coord()))
1302                    .unwrap_or_default();
1303                let thickness_texture: Option<Handle<Image>> =
1304                    volume.thickness_texture().map(|thickness_texture| {
1305                        textures
1306                            .get(thickness_texture.texture().index())
1307                            .cloned()
1308                            .unwrap_or_default()
1309                    });
1310
1311                (
1312                    volume.thickness_factor(),
1313                    thickness_channel,
1314                    thickness_texture,
1315                    volume.attenuation_distance(),
1316                    volume.attenuation_color(),
1317                )
1318            },
1319        );
1320
1321    #[cfg(not(feature = "pbr_transmission_textures"))]
1322    let (thickness, attenuation_distance, attenuation_color) =
1323        material
1324            .volume()
1325            .map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
1326                (
1327                    volume.thickness_factor(),
1328                    volume.attenuation_distance(),
1329                    volume.attenuation_color(),
1330                )
1331            });
1332
1333    let ior = material.ior().unwrap_or(1.5);
1334
1335    // Parse the `KHR_materials_clearcoat` extension data if necessary.
1336    let clearcoat =
1337        ClearcoatExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1338
1339    // Parse the `KHR_materials_anisotropy` extension data if necessary.
1340    let anisotropy =
1341        AnisotropyExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1342
1343    // Parse the `KHR_materials_specular` extension data if necessary.
1344    let specular =
1345        SpecularExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1346
1347    // We need to operate in the Linear color space and be willing to exceed 1.0 in our channels
1348    let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
1349    let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0);
1350
1351    let standard_material = StandardMaterial {
1352        base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]),
1353        base_color_channel,
1354        base_color_texture,
1355        perceptual_roughness: pbr.roughness_factor(),
1356        metallic: pbr.metallic_factor(),
1357        metallic_roughness_channel,
1358        metallic_roughness_texture,
1359        normal_map_channel,
1360        normal_map_texture,
1361        double_sided: material.double_sided(),
1362        cull_mode: if material.double_sided() {
1363            None
1364        } else if is_scale_inverted {
1365            Some(Face::Front)
1366        } else {
1367            Some(Face::Back)
1368        },
1369        occlusion_channel,
1370        occlusion_texture,
1371        emissive,
1372        emissive_channel,
1373        emissive_texture,
1374        specular_transmission,
1375        #[cfg(feature = "pbr_transmission_textures")]
1376        specular_transmission_channel,
1377        #[cfg(feature = "pbr_transmission_textures")]
1378        specular_transmission_texture,
1379        thickness,
1380        #[cfg(feature = "pbr_transmission_textures")]
1381        thickness_channel,
1382        #[cfg(feature = "pbr_transmission_textures")]
1383        thickness_texture,
1384        ior,
1385        attenuation_distance,
1386        attenuation_color: Color::linear_rgb(
1387            attenuation_color[0],
1388            attenuation_color[1],
1389            attenuation_color[2],
1390        ),
1391        unlit: material.unlit(),
1392        alpha_mode: alpha_mode(material),
1393        uv_transform,
1394        clearcoat: clearcoat.clearcoat_factor.unwrap_or_default() as f32,
1395        clearcoat_perceptual_roughness: clearcoat.clearcoat_roughness_factor.unwrap_or_default()
1396            as f32,
1397        #[cfg(feature = "pbr_multi_layer_material_textures")]
1398        clearcoat_channel: clearcoat.clearcoat_channel,
1399        #[cfg(feature = "pbr_multi_layer_material_textures")]
1400        clearcoat_texture: clearcoat.clearcoat_texture,
1401        #[cfg(feature = "pbr_multi_layer_material_textures")]
1402        clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel,
1403        #[cfg(feature = "pbr_multi_layer_material_textures")]
1404        clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture,
1405        #[cfg(feature = "pbr_multi_layer_material_textures")]
1406        clearcoat_normal_channel: clearcoat.clearcoat_normal_channel,
1407        #[cfg(feature = "pbr_multi_layer_material_textures")]
1408        clearcoat_normal_texture: clearcoat.clearcoat_normal_texture,
1409        anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32,
1410        anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32,
1411        #[cfg(feature = "pbr_anisotropy_texture")]
1412        anisotropy_channel: anisotropy.anisotropy_channel,
1413        #[cfg(feature = "pbr_anisotropy_texture")]
1414        anisotropy_texture: anisotropy.anisotropy_texture,
1415        // From the `KHR_materials_specular` spec:
1416        // <https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular#materials-with-reflectance-parameter>
1417        reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5,
1418        #[cfg(feature = "pbr_specular_textures")]
1419        specular_channel: specular.specular_channel,
1420        #[cfg(feature = "pbr_specular_textures")]
1421        specular_texture: specular.specular_texture,
1422        specular_tint: match specular.specular_color_factor {
1423            Some(color) => Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32),
1424            None => Color::WHITE,
1425        },
1426        #[cfg(feature = "pbr_specular_textures")]
1427        specular_tint_channel: specular.specular_color_channel,
1428        #[cfg(feature = "pbr_specular_textures")]
1429        specular_tint_texture: specular.specular_color_texture,
1430        ..Default::default()
1431    };
1432
1433    (
1434        material_label(material, is_scale_inverted).to_string(),
1435        standard_material,
1436    )
1437}
1438
1439/// Loads a glTF node.
1440#[cfg_attr(
1441    not(target_arch = "wasm32"),
1442    expect(
1443        clippy::result_large_err,
1444        reason = "`GltfError` is only barely past the threshold for large errors."
1445    )
1446)]
1447fn load_node(
1448    gltf_node: &Node,
1449    child_spawner: &mut ChildSpawner,
1450    root_load_context: &LoadContext,
1451    load_context: &mut LoadContext,
1452    settings: &GltfLoaderSettings,
1453    node_index_to_entity_map: &mut HashMap<usize, Entity>,
1454    entity_to_skin_index_map: &mut EntityHashMap<usize>,
1455    active_camera_found: &mut bool,
1456    parent_transform: &Transform,
1457    #[cfg(feature = "bevy_animation")] animation_roots: &HashSet<usize>,
1458    #[cfg(feature = "bevy_animation")] mut animation_context: Option<AnimationContext>,
1459    textures: &[Handle<Image>],
1460    convert_coordinates: &GltfConvertCoordinates,
1461    extensions: &mut [Box<dyn extensions::GltfExtensionHandler>],
1462) -> Result<(), GltfError> {
1463    let mut gltf_error = None;
1464    let transform = node_transform(gltf_node);
1465    let world_transform = *parent_transform * transform;
1466    // according to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#instantiation,
1467    // if the determinant of the transform is negative we must invert the winding order of
1468    // triangles in meshes on the node.
1469    // instead we equivalently test if the global scale is inverted by checking if the number
1470    // of negative scale factors is odd. if so we will assign a copy of the material with face
1471    // culling inverted, rather than modifying the mesh data directly.
1472    let is_scale_inverted = world_transform.scale.is_negative_bitmask().count_ones() & 1 == 1;
1473    let mut node = child_spawner.spawn((transform, Visibility::default()));
1474
1475    let name = node_name(gltf_node);
1476    node.insert(name.clone());
1477
1478    #[cfg(feature = "bevy_animation")]
1479    if animation_context.is_none() && animation_roots.contains(&gltf_node.index()) {
1480        // This is an animation root. Make a new animation context.
1481        animation_context = Some(AnimationContext {
1482            root: node.id(),
1483            path: SmallVec::new(),
1484        });
1485    }
1486
1487    #[cfg(feature = "bevy_animation")]
1488    if let Some(ref mut animation_context) = animation_context {
1489        animation_context.path.push(name);
1490
1491        node.insert((
1492            AnimationTargetId::from_names(animation_context.path.iter()),
1493            AnimatedBy(animation_context.root),
1494        ));
1495    }
1496
1497    if let Some(extras) = gltf_node.extras() {
1498        node.insert(GltfExtras {
1499            value: extras.get().to_string(),
1500        });
1501    }
1502
1503    // create camera node
1504    if settings.load_cameras
1505        && let Some(camera) = gltf_node.camera()
1506    {
1507        let projection = match camera.projection() {
1508            gltf::camera::Projection::Orthographic(orthographic) => {
1509                let xmag = orthographic.xmag();
1510                let orthographic_projection = OrthographicProjection {
1511                    near: orthographic.znear(),
1512                    far: orthographic.zfar(),
1513                    scaling_mode: ScalingMode::FixedHorizontal {
1514                        viewport_width: xmag,
1515                    },
1516                    ..OrthographicProjection::default_3d()
1517                };
1518                Projection::Orthographic(orthographic_projection)
1519            }
1520            gltf::camera::Projection::Perspective(perspective) => {
1521                let mut perspective_projection: PerspectiveProjection = PerspectiveProjection {
1522                    fov: perspective.yfov(),
1523                    near: perspective.znear(),
1524                    ..Default::default()
1525                };
1526                if let Some(zfar) = perspective.zfar() {
1527                    perspective_projection.far = zfar;
1528                }
1529                if let Some(aspect_ratio) = perspective.aspect_ratio() {
1530                    perspective_projection.aspect_ratio = aspect_ratio;
1531                }
1532                Projection::Perspective(perspective_projection)
1533            }
1534        };
1535
1536        node.insert((
1537            Camera3d::default(),
1538            projection,
1539            transform,
1540            Camera {
1541                is_active: !*active_camera_found,
1542                ..Default::default()
1543            },
1544        ));
1545
1546        *active_camera_found = true;
1547    }
1548
1549    // Map node index to entity
1550    node_index_to_entity_map.insert(gltf_node.index(), node.id());
1551
1552    let mut morph_weights = None;
1553
1554    node.with_children(|parent| {
1555        // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag
1556        if !settings.load_meshes.is_empty()
1557            && let Some(mesh) = gltf_node.mesh()
1558        {
1559            // append primitives
1560            for primitive in mesh.primitives() {
1561                let material = primitive.material();
1562                let material_label = material_label(&material, is_scale_inverted).to_string();
1563
1564                // This will make sure we load the default material now since it would not have been
1565                // added when iterating over all the gltf materials (since the default material is
1566                // not explicitly listed in the gltf).
1567                // It also ensures an inverted scale copy is instantiated if required.
1568                if !root_load_context.has_labeled_asset(&material_label)
1569                    && !load_context.has_labeled_asset(&material_label)
1570                {
1571                    let (label, material) = load_material(
1572                        &material,
1573                        textures,
1574                        is_scale_inverted,
1575                        load_context.path().clone(),
1576                    );
1577                    load_context.add_labeled_asset(label, material);
1578                }
1579
1580                let primitive_label = GltfAssetLabel::Primitive {
1581                    mesh: mesh.index(),
1582                    primitive: primitive.index(),
1583                };
1584                let bounds = primitive.bounding_box();
1585
1586                // Apply the inverse of the conversion transform that's been
1587                // applied to the mesh asset. This preserves the mesh's relation
1588                // to the node transform.
1589                let mesh_entity_transform = convert_coordinates.mesh_conversion_transform_inverse();
1590
1591                let mut mesh_entity = parent.spawn((
1592                    // TODO: handle missing label handle errors here?
1593                    Mesh3d(load_context.get_label_handle(primitive_label.to_string())),
1594                    MeshMaterial3d::<StandardMaterial>(
1595                        load_context.get_label_handle(&material_label),
1596                    ),
1597                    mesh_entity_transform,
1598                ));
1599
1600                let target_count = primitive.morph_targets().len();
1601                if target_count != 0 {
1602                    let weights = match mesh.weights() {
1603                        Some(weights) => weights.to_vec(),
1604                        None => vec![0.0; target_count],
1605                    };
1606
1607                    if morph_weights.is_none() {
1608                        morph_weights = Some(weights.clone());
1609                    }
1610
1611                    // unwrap: the parent's call to `MeshMorphWeights::new`
1612                    // means this code doesn't run if it returns an `Err`.
1613                    // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets
1614                    // they should all have the same length.
1615                    // > All morph target accessors MUST have the same count as
1616                    // > the accessors of the original primitive.
1617                    mesh_entity.insert(MeshMorphWeights::new(weights).unwrap());
1618                }
1619
1620                let mut bounds_min = Vec3::from_slice(&bounds.min);
1621                let mut bounds_max = Vec3::from_slice(&bounds.max);
1622
1623                if convert_coordinates.rotate_meshes {
1624                    let converted_min = bounds_min.convert_coordinates();
1625                    let converted_max = bounds_max.convert_coordinates();
1626
1627                    bounds_min = converted_min.min(converted_max);
1628                    bounds_max = converted_min.max(converted_max);
1629                }
1630
1631                mesh_entity.insert(Aabb::from_min_max(bounds_min, bounds_max));
1632
1633                if let Some(extras) = primitive.extras() {
1634                    mesh_entity.insert(GltfExtras {
1635                        value: extras.get().to_string(),
1636                    });
1637                }
1638
1639                if let Some(extras) = mesh.extras() {
1640                    mesh_entity.insert(GltfMeshExtras {
1641                        value: extras.get().to_string(),
1642                    });
1643                }
1644
1645                if let Some(extras) = material.extras() {
1646                    mesh_entity.insert(GltfMaterialExtras {
1647                        value: extras.get().to_string(),
1648                    });
1649                }
1650
1651                if let Some(name) = mesh.name() {
1652                    mesh_entity.insert(GltfMeshName(name.to_string()));
1653                }
1654
1655                if let Some(name) = material.name() {
1656                    mesh_entity.insert(GltfMaterialName(name.to_string()));
1657                }
1658
1659                mesh_entity.insert(Name::new(primitive_name(&mesh, &material)));
1660
1661                // Mark for adding skinned mesh
1662                if let Some(skin) = gltf_node.skin() {
1663                    entity_to_skin_index_map.insert(mesh_entity.id(), skin.index());
1664                }
1665
1666                // enable extension processing for a Bevy-created construct
1667                // that is the Mesh and Material merged on a single entity
1668                for extension in extensions.iter_mut() {
1669                    extension.on_spawn_mesh_and_material(
1670                        load_context,
1671                        &primitive,
1672                        &mesh,
1673                        &material,
1674                        &mut mesh_entity,
1675                    );
1676                }
1677            }
1678        }
1679
1680        if settings.load_lights
1681            && let Some(light) = gltf_node.light()
1682        {
1683            match light.kind() {
1684                gltf::khr_lights_punctual::Kind::Directional => {
1685                    let mut entity = parent.spawn(DirectionalLight {
1686                        color: Color::srgb_from_array(light.color()),
1687                        // NOTE: KHR_punctual_lights defines the intensity units for directional
1688                        // lights in lux (lm/m^2) which is what we need.
1689                        illuminance: light.intensity(),
1690                        ..Default::default()
1691                    });
1692                    if let Some(name) = light.name() {
1693                        entity.insert(Name::new(name.to_string()));
1694                    }
1695                    if let Some(extras) = light.extras() {
1696                        entity.insert(GltfExtras {
1697                            value: extras.get().to_string(),
1698                        });
1699                    }
1700                    for extension in extensions.iter_mut() {
1701                        extension.on_spawn_light_directional(load_context, gltf_node, &mut entity);
1702                    }
1703                }
1704                gltf::khr_lights_punctual::Kind::Point => {
1705                    let mut entity = parent.spawn(PointLight {
1706                        color: Color::srgb_from_array(light.color()),
1707                        // NOTE: KHR_punctual_lights defines the intensity units for point lights in
1708                        // candela (lm/sr) which is luminous intensity and we need luminous power.
1709                        // For a point light, luminous power = 4 * pi * luminous intensity
1710                        intensity: light.intensity() * core::f32::consts::PI * 4.0,
1711                        range: light.range().unwrap_or(20.0),
1712                        radius: 0.0,
1713                        ..Default::default()
1714                    });
1715                    if let Some(name) = light.name() {
1716                        entity.insert(Name::new(name.to_string()));
1717                    }
1718                    if let Some(extras) = light.extras() {
1719                        entity.insert(GltfExtras {
1720                            value: extras.get().to_string(),
1721                        });
1722                    }
1723                    for extension in extensions.iter_mut() {
1724                        extension.on_spawn_light_point(load_context, gltf_node, &mut entity);
1725                    }
1726                }
1727                gltf::khr_lights_punctual::Kind::Spot {
1728                    inner_cone_angle,
1729                    outer_cone_angle,
1730                } => {
1731                    let mut entity = parent.spawn(SpotLight {
1732                        color: Color::srgb_from_array(light.color()),
1733                        // NOTE: KHR_punctual_lights defines the intensity units for spot lights in
1734                        // candela (lm/sr) which is luminous intensity and we need luminous power.
1735                        // For a spot light, we map luminous power = 4 * pi * luminous intensity
1736                        intensity: light.intensity() * core::f32::consts::PI * 4.0,
1737                        range: light.range().unwrap_or(20.0),
1738                        radius: light.range().unwrap_or(0.0),
1739                        inner_angle: inner_cone_angle,
1740                        outer_angle: outer_cone_angle,
1741                        ..Default::default()
1742                    });
1743                    if let Some(name) = light.name() {
1744                        entity.insert(Name::new(name.to_string()));
1745                    }
1746                    if let Some(extras) = light.extras() {
1747                        entity.insert(GltfExtras {
1748                            value: extras.get().to_string(),
1749                        });
1750                    }
1751                    for extension in extensions.iter_mut() {
1752                        extension.on_spawn_light_spot(load_context, gltf_node, &mut entity);
1753                    }
1754                }
1755            }
1756        }
1757
1758        // append other nodes
1759        for child in gltf_node.children() {
1760            if let Err(err) = load_node(
1761                &child,
1762                parent,
1763                root_load_context,
1764                load_context,
1765                settings,
1766                node_index_to_entity_map,
1767                entity_to_skin_index_map,
1768                active_camera_found,
1769                &world_transform,
1770                #[cfg(feature = "bevy_animation")]
1771                animation_roots,
1772                #[cfg(feature = "bevy_animation")]
1773                animation_context.clone(),
1774                textures,
1775                convert_coordinates,
1776                extensions,
1777            ) {
1778                gltf_error = Some(err);
1779                return;
1780            }
1781        }
1782    });
1783
1784    // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag
1785    if !settings.load_meshes.is_empty()
1786        && let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights)
1787    {
1788        let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive {
1789            mesh: mesh.index(),
1790            primitive: p.index(),
1791        });
1792        let first_mesh =
1793            primitive_label.map(|label| load_context.get_label_handle(label.to_string()));
1794        node.insert(MorphWeights::new(weights, first_mesh)?);
1795    }
1796
1797    // let extensions process node data
1798    // This can be *many* kinds of object, so we also
1799    // give access to the gltf_node, which is needed for
1800    // accessing Mesh and Material extension data, which
1801    // are merged onto the same entity in Bevy
1802    for extension in extensions.iter_mut() {
1803        extension.on_gltf_node(load_context, gltf_node, &mut node);
1804    }
1805
1806    if let Some(err) = gltf_error {
1807        Err(err)
1808    } else {
1809        Ok(())
1810    }
1811}
1812
1813/// Loads the raw glTF buffer data for a specific glTF file.
1814async fn load_buffers(
1815    gltf: &gltf::Gltf,
1816    load_context: &mut LoadContext<'_>,
1817) -> Result<Vec<Vec<u8>>, GltfError> {
1818    const VALID_MIME_TYPES: &[&str] = &["application/octet-stream", "application/gltf-buffer"];
1819
1820    let mut buffer_data = Vec::new();
1821    for buffer in gltf.buffers() {
1822        match buffer.source() {
1823            gltf::buffer::Source::Uri(uri) => {
1824                let uri = percent_encoding::percent_decode_str(uri)
1825                    .decode_utf8()
1826                    .unwrap();
1827                let uri = uri.as_ref();
1828                let buffer_bytes = match DataUri::parse(uri) {
1829                    Ok(data_uri) if VALID_MIME_TYPES.contains(&data_uri.mime_type) => {
1830                        data_uri.decode()?
1831                    }
1832                    Ok(_) => return Err(GltfError::BufferFormatUnsupported),
1833                    Err(()) => {
1834                        // TODO: Remove this and add dep
1835                        let buffer_path = load_context
1836                            .path()
1837                            .resolve_embed(uri)
1838                            .map_err(|err| GltfError::InvalidBufferUri(uri.to_owned(), err))?;
1839                        load_context.read_asset_bytes(buffer_path).await?
1840                    }
1841                };
1842                buffer_data.push(buffer_bytes);
1843            }
1844            gltf::buffer::Source::Bin => {
1845                if let Some(blob) = gltf.blob.as_deref() {
1846                    buffer_data.push(blob.into());
1847                } else {
1848                    return Err(GltfError::MissingBlob);
1849                }
1850            }
1851        }
1852    }
1853
1854    Ok(buffer_data)
1855}
1856
1857struct DataUri<'a> {
1858    pub mime_type: &'a str,
1859    pub base64: bool,
1860    pub data: &'a str,
1861}
1862
1863impl<'a> DataUri<'a> {
1864    fn parse(uri: &'a str) -> Result<DataUri<'a>, ()> {
1865        let uri = uri.strip_prefix("data:").ok_or(())?;
1866        let (mime_type, data) = Self::split_once(uri, ',').ok_or(())?;
1867
1868        let (mime_type, base64) = match mime_type.strip_suffix(";base64") {
1869            Some(mime_type) => (mime_type, true),
1870            None => (mime_type, false),
1871        };
1872
1873        Ok(DataUri {
1874            mime_type,
1875            base64,
1876            data,
1877        })
1878    }
1879
1880    fn decode(&self) -> Result<Vec<u8>, base64::DecodeError> {
1881        if self.base64 {
1882            base64::Engine::decode(&base64::engine::general_purpose::STANDARD, self.data)
1883        } else {
1884            Ok(self.data.as_bytes().to_owned())
1885        }
1886    }
1887
1888    fn split_once(input: &str, delimiter: char) -> Option<(&str, &str)> {
1889        let mut iter = input.splitn(2, delimiter);
1890        Some((iter.next()?, iter.next()?))
1891    }
1892}
1893
1894enum ImageOrPath {
1895    Image {
1896        image: Image,
1897        label: GltfAssetLabel,
1898    },
1899    Path {
1900        path: AssetPath<'static>,
1901        is_srgb: bool,
1902        sampler_descriptor: ImageSamplerDescriptor,
1903        render_asset_usages: RenderAssetUsages,
1904    },
1905}
1906
1907impl ImageOrPath {
1908    // TODO: use the threaded impl on wasm once wasm thread pool doesn't deadlock on it
1909    // See https://github.com/bevyengine/bevy/issues/1924 for more details
1910    // The taskpool use is also avoided when there is only one texture for performance reasons and
1911    // to avoid https://github.com/bevyengine/bevy/pull/2725
1912    // PERF: could this be a Vec instead? Are gltf texture indices dense?
1913    fn process_loaded_texture(
1914        self,
1915        load_context: &mut LoadContext,
1916        handles: &mut Vec<Handle<Image>>,
1917    ) {
1918        let handle = match self {
1919            ImageOrPath::Image { label, image } => {
1920                load_context.add_labeled_asset(label.to_string(), image)
1921            }
1922            ImageOrPath::Path {
1923                path,
1924                is_srgb,
1925                sampler_descriptor,
1926                render_asset_usages,
1927            } => load_context
1928                .loader()
1929                .with_settings(move |settings: &mut ImageLoaderSettings| {
1930                    settings.is_srgb = is_srgb;
1931                    settings.sampler = ImageSampler::Descriptor(sampler_descriptor.clone());
1932                    settings.asset_usage = render_asset_usages;
1933                })
1934                .load(path),
1935        };
1936        handles.push(handle);
1937    }
1938}
1939
1940struct PrimitiveMorphAttributesIter<'s> {
1941    convert_coordinates: bool,
1942    positions: Option<Iter<'s, [f32; 3]>>,
1943    normals: Option<Iter<'s, [f32; 3]>>,
1944    tangents: Option<Iter<'s, [f32; 3]>>,
1945}
1946
1947impl<'s> Iterator for PrimitiveMorphAttributesIter<'s> {
1948    type Item = MorphAttributes;
1949
1950    fn next(&mut self) -> Option<Self::Item> {
1951        let position = self.positions.as_mut().and_then(Iterator::next);
1952        let normal = self.normals.as_mut().and_then(Iterator::next);
1953        let tangent = self.tangents.as_mut().and_then(Iterator::next);
1954        if position.is_none() && normal.is_none() && tangent.is_none() {
1955            return None;
1956        }
1957
1958        let mut attributes = MorphAttributes {
1959            position: position.map(Into::into).unwrap_or(Vec3::ZERO),
1960            normal: normal.map(Into::into).unwrap_or(Vec3::ZERO),
1961            tangent: tangent.map(Into::into).unwrap_or(Vec3::ZERO),
1962        };
1963
1964        if self.convert_coordinates {
1965            attributes = MorphAttributes {
1966                position: attributes.position.convert_coordinates(),
1967                normal: attributes.normal.convert_coordinates(),
1968                tangent: attributes.tangent.convert_coordinates(),
1969            }
1970        }
1971
1972        Some(attributes)
1973    }
1974}
1975
1976/// A helper structure for `load_node` that contains information about the
1977/// nearest ancestor animation root.
1978#[cfg(feature = "bevy_animation")]
1979#[derive(Clone)]
1980struct AnimationContext {
1981    /// The nearest ancestor animation root.
1982    pub root: Entity,
1983    /// The path to the animation root. This is used for constructing the
1984    /// animation target UUIDs.
1985    pub path: SmallVec<[Name; 8]>,
1986}
1987
1988#[derive(Deserialize)]
1989#[serde(rename_all = "camelCase")]
1990struct MorphTargetNames {
1991    pub target_names: Vec<String>,
1992}
1993
1994#[cfg(test)]
1995mod test {
1996    use std::path::Path;
1997
1998    use crate::{Gltf, GltfAssetLabel, GltfNode, GltfSkin};
1999    use bevy_app::{App, TaskPoolPlugin};
2000    use bevy_asset::{
2001        io::{
2002            memory::{Dir, MemoryAssetReader},
2003            AssetSourceBuilder, AssetSourceId,
2004        },
2005        AssetApp, AssetLoader, AssetPlugin, AssetServer, Assets, Handle, LoadState,
2006    };
2007    use bevy_ecs::{resource::Resource, world::World};
2008    use bevy_image::{Image, ImageLoaderSettings};
2009    use bevy_log::LogPlugin;
2010    use bevy_mesh::skinning::SkinnedMeshInverseBindposes;
2011    use bevy_mesh::MeshPlugin;
2012    use bevy_pbr::StandardMaterial;
2013    use bevy_reflect::TypePath;
2014    use bevy_scene::ScenePlugin;
2015
2016    fn test_app(dir: Dir) -> App {
2017        let mut app = App::new();
2018        let reader = MemoryAssetReader { root: dir };
2019        app.register_asset_source(
2020            AssetSourceId::Default,
2021            AssetSourceBuilder::new(move || Box::new(reader.clone())),
2022        )
2023        .add_plugins((
2024            LogPlugin::default(),
2025            TaskPoolPlugin::default(),
2026            AssetPlugin::default(),
2027            ScenePlugin,
2028            MeshPlugin,
2029            crate::GltfPlugin::default(),
2030        ));
2031
2032        app.finish();
2033        app.cleanup();
2034
2035        app
2036    }
2037
2038    const LARGE_ITERATION_COUNT: usize = 10000;
2039
2040    fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
2041        for _ in 0..LARGE_ITERATION_COUNT {
2042            app.update();
2043            if predicate(app.world_mut()).is_some() {
2044                return;
2045            }
2046        }
2047
2048        panic!("Ran out of loops to return `Some` from `predicate`");
2049    }
2050
2051    fn load_gltf_into_app(gltf_path: &str, gltf: &str) -> App {
2052        #[expect(
2053            dead_code,
2054            reason = "This struct is used to keep the handle alive. As such, we have no need to handle the handle directly."
2055        )]
2056        #[derive(Resource)]
2057        struct GltfHandle(Handle<Gltf>);
2058
2059        let dir = Dir::default();
2060        dir.insert_asset_text(Path::new(gltf_path), gltf);
2061        let mut app = test_app(dir);
2062        app.update();
2063        let asset_server = app.world().resource::<AssetServer>().clone();
2064        let handle: Handle<Gltf> = asset_server.load(gltf_path.to_string());
2065        let handle_id = handle.id();
2066        app.insert_resource(GltfHandle(handle));
2067        app.update();
2068        run_app_until(&mut app, |_world| {
2069            let load_state = asset_server.get_load_state(handle_id).unwrap();
2070            match load_state {
2071                LoadState::Loaded => Some(()),
2072                LoadState::Failed(err) => panic!("{err}"),
2073                _ => None,
2074            }
2075        });
2076        app
2077    }
2078
2079    #[test]
2080    fn single_node() {
2081        let gltf_path = "test.gltf";
2082        let app = load_gltf_into_app(
2083            gltf_path,
2084            r#"
2085{
2086    "asset": {
2087        "version": "2.0"
2088    },
2089    "nodes": [
2090        {
2091            "name": "TestSingleNode"
2092        }
2093    ],
2094    "scene": 0,
2095    "scenes": [{ "nodes": [0] }]
2096}
2097"#,
2098        );
2099        let asset_server = app.world().resource::<AssetServer>();
2100        let handle = asset_server.load(gltf_path);
2101        let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
2102        let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
2103        let gltf_root = gltf_root_assets.get(&handle).unwrap();
2104        assert!(gltf_root.nodes.len() == 1, "Single node");
2105        assert!(
2106            gltf_root.named_nodes.contains_key("TestSingleNode"),
2107            "Named node is in named nodes"
2108        );
2109        let gltf_node = gltf_node_assets
2110            .get(gltf_root.named_nodes.get("TestSingleNode").unwrap())
2111            .unwrap();
2112        assert_eq!(gltf_node.name, "TestSingleNode", "Correct name");
2113        assert_eq!(gltf_node.index, 0, "Correct index");
2114        assert_eq!(gltf_node.children.len(), 0, "No children");
2115        assert_eq!(gltf_node.asset_label(), GltfAssetLabel::Node(0));
2116    }
2117
2118    #[test]
2119    fn node_hierarchy_no_hierarchy() {
2120        let gltf_path = "test.gltf";
2121        let app = load_gltf_into_app(
2122            gltf_path,
2123            r#"
2124{
2125    "asset": {
2126        "version": "2.0"
2127    },
2128    "nodes": [
2129        {
2130            "name": "l1"
2131        },
2132        {
2133            "name": "l2"
2134        }
2135    ],
2136    "scene": 0,
2137    "scenes": [{ "nodes": [0] }]
2138}
2139"#,
2140        );
2141        let asset_server = app.world().resource::<AssetServer>();
2142        let handle = asset_server.load(gltf_path);
2143        let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
2144        let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
2145        let gltf_root = gltf_root_assets.get(&handle).unwrap();
2146        let result = gltf_root
2147            .nodes
2148            .iter()
2149            .map(|h| gltf_node_assets.get(h).unwrap())
2150            .collect::<Vec<_>>();
2151        assert_eq!(result.len(), 2);
2152        assert_eq!(result[0].name, "l1");
2153        assert_eq!(result[0].children.len(), 0);
2154        assert_eq!(result[1].name, "l2");
2155        assert_eq!(result[1].children.len(), 0);
2156    }
2157
2158    #[test]
2159    fn node_hierarchy_simple_hierarchy() {
2160        let gltf_path = "test.gltf";
2161        let app = load_gltf_into_app(
2162            gltf_path,
2163            r#"
2164{
2165    "asset": {
2166        "version": "2.0"
2167    },
2168    "nodes": [
2169        {
2170            "name": "l1",
2171            "children": [1]
2172        },
2173        {
2174            "name": "l2"
2175        }
2176    ],
2177    "scene": 0,
2178    "scenes": [{ "nodes": [0] }]
2179}
2180"#,
2181        );
2182        let asset_server = app.world().resource::<AssetServer>();
2183        let handle = asset_server.load(gltf_path);
2184        let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
2185        let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
2186        let gltf_root = gltf_root_assets.get(&handle).unwrap();
2187        let result = gltf_root
2188            .nodes
2189            .iter()
2190            .map(|h| gltf_node_assets.get(h).unwrap())
2191            .collect::<Vec<_>>();
2192        assert_eq!(result.len(), 2);
2193        assert_eq!(result[0].name, "l1");
2194        assert_eq!(result[0].children.len(), 1);
2195        assert_eq!(result[1].name, "l2");
2196        assert_eq!(result[1].children.len(), 0);
2197    }
2198
2199    #[test]
2200    fn node_hierarchy_hierarchy() {
2201        let gltf_path = "test.gltf";
2202        let app = load_gltf_into_app(
2203            gltf_path,
2204            r#"
2205{
2206    "asset": {
2207        "version": "2.0"
2208    },
2209    "nodes": [
2210        {
2211            "name": "l1",
2212            "children": [1]
2213        },
2214        {
2215            "name": "l2",
2216            "children": [2]
2217        },
2218        {
2219            "name": "l3",
2220            "children": [3, 4, 5]
2221        },
2222        {
2223            "name": "l4",
2224            "children": [6]
2225        },
2226        {
2227            "name": "l5"
2228        },
2229        {
2230            "name": "l6"
2231        },
2232        {
2233            "name": "l7"
2234        }
2235    ],
2236    "scene": 0,
2237    "scenes": [{ "nodes": [0] }]
2238}
2239"#,
2240        );
2241        let asset_server = app.world().resource::<AssetServer>();
2242        let handle = asset_server.load(gltf_path);
2243        let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
2244        let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
2245        let gltf_root = gltf_root_assets.get(&handle).unwrap();
2246        let result = gltf_root
2247            .nodes
2248            .iter()
2249            .map(|h| gltf_node_assets.get(h).unwrap())
2250            .collect::<Vec<_>>();
2251        assert_eq!(result.len(), 7);
2252        assert_eq!(result[0].name, "l1");
2253        assert_eq!(result[0].children.len(), 1);
2254        assert_eq!(result[1].name, "l2");
2255        assert_eq!(result[1].children.len(), 1);
2256        assert_eq!(result[2].name, "l3");
2257        assert_eq!(result[2].children.len(), 3);
2258        assert_eq!(result[3].name, "l4");
2259        assert_eq!(result[3].children.len(), 1);
2260        assert_eq!(result[4].name, "l5");
2261        assert_eq!(result[4].children.len(), 0);
2262        assert_eq!(result[5].name, "l6");
2263        assert_eq!(result[5].children.len(), 0);
2264        assert_eq!(result[6].name, "l7");
2265        assert_eq!(result[6].children.len(), 0);
2266    }
2267
2268    #[test]
2269    fn node_hierarchy_cyclic() {
2270        let gltf_path = "test.gltf";
2271        let gltf_str = r#"
2272{
2273    "asset": {
2274        "version": "2.0"
2275    },
2276    "nodes": [
2277        {
2278            "name": "l1",
2279            "children": [1]
2280        },
2281        {
2282            "name": "l2",
2283            "children": [0]
2284        }
2285    ],
2286    "scene": 0,
2287    "scenes": [{ "nodes": [0] }]
2288}
2289"#;
2290
2291        let dir = Dir::default();
2292        dir.insert_asset_text(Path::new(gltf_path), gltf_str);
2293        let mut app = test_app(dir);
2294        app.update();
2295        let asset_server = app.world().resource::<AssetServer>().clone();
2296        let handle: Handle<Gltf> = asset_server.load(gltf_path);
2297        let handle_id = handle.id();
2298        app.update();
2299        run_app_until(&mut app, |_world| {
2300            let load_state = asset_server.get_load_state(handle_id).unwrap();
2301            if load_state.is_failed() {
2302                Some(())
2303            } else {
2304                None
2305            }
2306        });
2307        let load_state = asset_server.get_load_state(handle_id).unwrap();
2308        assert!(load_state.is_failed());
2309    }
2310
2311    #[test]
2312    fn node_hierarchy_missing_node() {
2313        let gltf_path = "test.gltf";
2314        let gltf_str = r#"
2315{
2316    "asset": {
2317        "version": "2.0"
2318    },
2319    "nodes": [
2320        {
2321            "name": "l1",
2322            "children": [2]
2323        },
2324        {
2325            "name": "l2"
2326        }
2327    ],
2328    "scene": 0,
2329    "scenes": [{ "nodes": [0] }]
2330}
2331"#;
2332
2333        let dir = Dir::default();
2334        dir.insert_asset_text(Path::new(gltf_path), gltf_str);
2335        let mut app = test_app(dir);
2336        app.update();
2337        let asset_server = app.world().resource::<AssetServer>().clone();
2338        let handle: Handle<Gltf> = asset_server.load(gltf_path);
2339        let handle_id = handle.id();
2340        app.update();
2341        run_app_until(&mut app, |_world| {
2342            let load_state = asset_server.get_load_state(handle_id).unwrap();
2343            if load_state.is_failed() {
2344                Some(())
2345            } else {
2346                None
2347            }
2348        });
2349        let load_state = asset_server.get_load_state(handle_id).unwrap();
2350        assert!(load_state.is_failed());
2351    }
2352
2353    #[test]
2354    fn skin_node() {
2355        let gltf_path = "test.gltf";
2356        let app = load_gltf_into_app(
2357            gltf_path,
2358            r#"
2359{
2360    "asset": {
2361        "version": "2.0"
2362    },
2363    "nodes": [
2364        {
2365            "name": "skinned",
2366            "skin": 0,
2367            "children": [1, 2]
2368        },
2369        {
2370            "name": "joint1"
2371        },
2372        {
2373            "name": "joint2"
2374        }
2375    ],
2376    "skins": [
2377        {
2378            "inverseBindMatrices": 0,
2379            "joints": [1, 2]
2380        }
2381    ],
2382    "buffers": [
2383        {
2384            "uri" : "data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=",
2385            "byteLength" : 128
2386        }
2387    ],
2388    "bufferViews": [
2389        {
2390            "buffer": 0,
2391            "byteLength": 128
2392        }
2393    ],
2394    "accessors": [
2395        {
2396            "bufferView" : 0,
2397            "componentType" : 5126,
2398            "count" : 2,
2399            "type" : "MAT4"
2400        }
2401    ],
2402    "scene": 0,
2403    "scenes": [{ "nodes": [0] }]
2404}
2405"#,
2406        );
2407        let asset_server = app.world().resource::<AssetServer>();
2408        let handle = asset_server.load(gltf_path);
2409        let gltf_root_assets = app.world().resource::<Assets<Gltf>>();
2410        let gltf_node_assets = app.world().resource::<Assets<GltfNode>>();
2411        let gltf_skin_assets = app.world().resource::<Assets<GltfSkin>>();
2412        let gltf_inverse_bind_matrices = app
2413            .world()
2414            .resource::<Assets<SkinnedMeshInverseBindposes>>();
2415        let gltf_root = gltf_root_assets.get(&handle).unwrap();
2416
2417        assert_eq!(gltf_root.skins.len(), 1);
2418        assert_eq!(gltf_root.nodes.len(), 3);
2419
2420        let skin = gltf_skin_assets.get(&gltf_root.skins[0]).unwrap();
2421        assert_eq!(skin.joints.len(), 2);
2422        assert_eq!(skin.joints[0], gltf_root.nodes[1]);
2423        assert_eq!(skin.joints[1], gltf_root.nodes[2]);
2424        assert!(gltf_inverse_bind_matrices.contains(&skin.inverse_bind_matrices));
2425
2426        let skinned_node = gltf_node_assets.get(&gltf_root.nodes[0]).unwrap();
2427        assert_eq!(skinned_node.name, "skinned");
2428        assert_eq!(skinned_node.children.len(), 2);
2429        assert_eq!(skinned_node.skin.as_ref(), Some(&gltf_root.skins[0]));
2430    }
2431
2432    fn test_app_custom_asset_source() -> (App, Dir) {
2433        let dir = Dir::default();
2434
2435        let mut app = App::new();
2436        let custom_reader = MemoryAssetReader { root: dir.clone() };
2437        // Create a default asset source so we definitely don't try to read from disk.
2438        app.register_asset_source(
2439            AssetSourceId::Default,
2440            AssetSourceBuilder::new(move || {
2441                Box::new(MemoryAssetReader {
2442                    root: Dir::default(),
2443                })
2444            }),
2445        )
2446        .register_asset_source(
2447            "custom",
2448            AssetSourceBuilder::new(move || Box::new(custom_reader.clone())),
2449        )
2450        .add_plugins((
2451            LogPlugin::default(),
2452            TaskPoolPlugin::default(),
2453            AssetPlugin::default(),
2454            ScenePlugin,
2455            MeshPlugin,
2456            crate::GltfPlugin::default(),
2457        ));
2458
2459        app.finish();
2460        app.cleanup();
2461
2462        (app, dir)
2463    }
2464
2465    #[test]
2466    fn reads_buffer_in_custom_asset_source() {
2467        let (mut app, dir) = test_app_custom_asset_source();
2468
2469        dir.insert_asset_text(
2470            Path::new("abc.gltf"),
2471            r#"
2472{
2473    "asset": {
2474        "version": "2.0"
2475    },
2476    "buffers": [
2477        {
2478            "uri": "abc.bin",
2479            "byteLength": 3
2480        }
2481    ]
2482}
2483"#,
2484        );
2485        // We don't care that the buffer contains reasonable info since we won't actually use it.
2486        dir.insert_asset_text(Path::new("abc.bin"), "Sup");
2487
2488        let asset_server = app.world().resource::<AssetServer>().clone();
2489        let handle: Handle<Gltf> = asset_server.load("custom://abc.gltf");
2490        run_app_until(&mut app, |_world| {
2491            let load_state = asset_server.get_load_state(handle.id()).unwrap();
2492            match load_state {
2493                LoadState::Loaded => Some(()),
2494                LoadState::Failed(err) => panic!("{err}"),
2495                _ => None,
2496            }
2497        });
2498    }
2499
2500    #[test]
2501    fn reads_images_in_custom_asset_source() {
2502        let (mut app, dir) = test_app_custom_asset_source();
2503
2504        app.init_asset::<StandardMaterial>();
2505
2506        // Note: We need the material here since otherwise we don't store the texture handle, which
2507        // can result in the image getting dropped leading to the gltf never being loaded with
2508        // dependencies.
2509        dir.insert_asset_text(
2510            Path::new("abc.gltf"),
2511            r#"
2512{
2513    "asset": {
2514        "version": "2.0"
2515    },
2516    "textures": [
2517        {
2518            "source": 0,
2519            "sampler": 0
2520        }
2521    ],
2522    "images": [
2523        {
2524            "uri": "abc.png"
2525        }
2526    ],
2527    "samplers": [
2528        {
2529            "magFilter": 9729,
2530            "minFilter": 9729
2531        }
2532    ],
2533    "materials": [
2534        {
2535            "pbrMetallicRoughness": {
2536                "baseColorTexture": {
2537                    "index": 0,
2538                    "texCoord": 0
2539                }
2540            }
2541        }
2542    ]
2543}
2544"#,
2545        );
2546        // We don't care that the image contains reasonable info since we won't actually use it.
2547        dir.insert_asset_text(Path::new("abc.png"), "Sup");
2548
2549        /// A fake loader to avoid actually loading any image data and just return an image.
2550        #[derive(TypePath)]
2551        struct FakePngLoader;
2552
2553        impl AssetLoader for FakePngLoader {
2554            type Asset = Image;
2555            type Error = std::io::Error;
2556            type Settings = ImageLoaderSettings;
2557
2558            async fn load(
2559                &self,
2560                _reader: &mut dyn bevy_asset::io::Reader,
2561                _settings: &Self::Settings,
2562                _load_context: &mut bevy_asset::LoadContext<'_>,
2563            ) -> Result<Self::Asset, Self::Error> {
2564                Ok(Image::default())
2565            }
2566
2567            fn extensions(&self) -> &[&str] {
2568                &["png"]
2569            }
2570        }
2571
2572        app.init_asset::<Image>()
2573            .register_asset_loader(FakePngLoader);
2574
2575        let asset_server = app.world().resource::<AssetServer>().clone();
2576        let handle: Handle<Gltf> = asset_server.load("custom://abc.gltf");
2577        run_app_until(&mut app, |_world| {
2578            // Note: we can't assert for failure since it's the nested load that fails, not the GLTF
2579            // load.
2580            asset_server
2581                .is_loaded_with_dependencies(&handle)
2582                .then_some(())
2583        });
2584    }
2585}