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#[derive(Error, Debug)]
82pub enum GltfError {
83 #[error("unsupported primitive mode")]
85 UnsupportedPrimitive {
86 mode: Mode,
88 },
89 #[error("invalid glTF file: {0}")]
91 Gltf(#[from] gltf::Error),
92 #[error("binary blob is missing")]
94 MissingBlob,
95 #[error("failed to decode base64 mesh data")]
97 Base64Decode(#[from] base64::DecodeError),
98 #[error("unsupported buffer format")]
100 BufferFormatUnsupported,
101 #[error("invalid buffer uri: {0}. asset path error={1}")]
103 InvalidBufferUri(String, ParseAssetPathError),
104 #[error("invalid image mime type: {0}")]
106 #[from(ignore)]
107 InvalidImageMimeType(String),
108 #[error("You may need to add the feature for the file format: {0}")]
110 ImageError(#[from] TextureError),
111 #[error("invalid image uri: {0}. asset path error={1}")]
113 InvalidImageUri(String, ParseAssetPathError),
114 #[error("failed to read bytes from an asset path: {0}")]
116 ReadAssetBytesError(#[from] ReadAssetBytesError),
117 #[error("failed to load asset from an asset path: {0}")]
119 AssetLoadError(#[from] AssetLoadError),
120 #[error("Missing sampler for animation {0}")]
122 #[from(ignore)]
123 MissingAnimationSampler(usize),
124 #[error("failed to generate tangents: {0}")]
126 GenerateTangentsError(#[from] bevy_mesh::GenerateTangentsError),
127 #[error("failed to generate morph targets: {0}")]
129 MorphTarget(#[from] bevy_mesh::morph::MorphBuildError),
130 #[error("GLTF model must be a tree, found cycle instead at node indices: {0:?}")]
132 #[from(ignore)]
133 CircularChildren(String),
134 #[error("failed to load file: {0}")]
136 Io(#[from] Error),
137}
138
139#[derive(TypePath)]
141pub struct GltfLoader {
142 pub supported_compressed_formats: CompressedImageFormats,
144 pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
150 pub default_sampler: Arc<Mutex<ImageSamplerDescriptor>>,
152 pub default_convert_coordinates: GltfConvertCoordinates,
155 pub extensions: Arc<RwLock<Vec<Box<dyn extensions::GltfExtensionHandler>>>>,
159}
160
161#[derive(Serialize, Deserialize)]
179pub struct GltfLoaderSettings {
180 pub load_meshes: RenderAssetUsages,
184 pub load_materials: RenderAssetUsages,
188 pub load_cameras: bool,
190 pub load_lights: bool,
192 pub load_animations: bool,
196 pub include_source: bool,
198 pub default_sampler: Option<ImageSamplerDescriptor>,
202 pub override_sampler: bool,
204 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 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 let mut extensions = loader.extensions.read().await.clone();
238
239 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 for extension in extensions.iter_mut() {
562 extension.on_animation(&animation, handle.clone());
563 }
564
565 animations.push(handle);
566 }
567
568 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 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 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 .zip(gltf.textures())
639 .for_each(|(result, texture)| match result {
640 Ok(image) => {
641 image.process_loaded_texture(load_context, &mut texture_handles);
642 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 if !settings.load_materials.is_empty() {
657 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 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 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 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 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 check_for_cycles(&gltf)?;
885
886 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 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 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
1114async 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
1183fn 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 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 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 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 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 let clearcoat =
1337 ClearcoatExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1338
1339 let anisotropy =
1341 AnisotropyExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1342
1343 let specular =
1345 SpecularExtension::parse(material, textures, asset_path.clone()).unwrap_or_default();
1346
1347 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 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#[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 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 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 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 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 if !settings.load_meshes.is_empty()
1557 && let Some(mesh) = gltf_node.mesh()
1558 {
1559 for primitive in mesh.primitives() {
1561 let material = primitive.material();
1562 let material_label = material_label(&material, is_scale_inverted).to_string();
1563
1564 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 let mesh_entity_transform = convert_coordinates.mesh_conversion_transform_inverse();
1590
1591 let mut mesh_entity = parent.spawn((
1592 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 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 if let Some(skin) = gltf_node.skin() {
1663 entity_to_skin_index_map.insert(mesh_entity.id(), skin.index());
1664 }
1665
1666 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 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 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 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 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 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 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
1813async 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 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 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#[cfg(feature = "bevy_animation")]
1979#[derive(Clone)]
1980struct AnimationContext {
1981 pub root: Entity,
1983 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 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 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 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 dir.insert_asset_text(Path::new("abc.png"), "Sup");
2548
2549 #[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 asset_server
2581 .is_loaded_with_dependencies(&handle)
2582 .then_some(())
2583 });
2584 }
2585}