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