1//! glTF extensions defined by the Khronos Group and other vendors
23mod khr_materials_anisotropy;
4mod khr_materials_clearcoat;
5mod khr_materials_specular;
67use alloc::sync::Arc;
8use async_lock::RwLock;
910use bevy_asset::{Handle, LoadContext};
11use bevy_ecs::{
12entity::Entity,
13resource::Resource,
14 world::{EntityWorldMut, World},
15};
16use bevy_mesh::{Mesh, MeshVertexAttribute};
17use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
18use gltf::Node;
1920use bevy_platform::collections::HashMap;
21#[cfg(feature = "bevy_animation")]
22use {bevy_animation::AnimationClip, bevy_platform::collections::HashSet};
2324use crate::{GltfLoaderSettings, GltfMaterial, GltfMesh};
2526pub(crate) use self::{
27khr_materials_anisotropy::AnisotropyExtension, khr_materials_clearcoat::ClearcoatExtension,
28khr_materials_specular::SpecularExtension,
29};
3031/// Stores the `ErasedGltfExtensionHandler` implementations so that they
32/// can be added by users and also passed to the glTF loader
33#[derive(impl bevy_ecs::component::Component for GltfExtensionHandlers where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<GltfExtensionHandlers>()
{
id
} else {
required_components.components_registrator().register_component::<GltfExtensionHandlers>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}
impl bevy_ecs::resource::Resource for GltfExtensionHandlers where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource, #[automatically_derived]impl ::core::default::Default for
GltfExtensionHandlers {
#[inline]
fn default() -> Self {
GltfExtensionHandlers(::core::default::Default::default())
}
}Default)]
34pub struct GltfExtensionHandlers(pub Arc<RwLock<Vec<Box<dyn ErasedGltfExtensionHandler>>>>);
3536/// glTF Extensions can attach data to any objects in a glTF file.
37/// This is done by inserting data in the `extensions` sub-object, and
38/// data in the extensions sub-object is keyed by the id of the extension.
39/// For example: `KHR_materials_variants`, `EXT_meshopt_compression`, or `BEVY_my_tool`
40///
41/// A list of publicly known extensions and their ids can be found
42/// in the [KhronosGroup/glTF](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md)
43/// git repo. Vendors reserve prefixes, such as the `BEVY` prefix,
44/// which is also listed in the [KhronosGroup repo](https://github.com/KhronosGroup/glTF/blob/main/extensions/Prefixes.md).
45///
46/// The `GltfExtensionHandler` trait should be implemented to participate in
47/// processing glTF files as they load, and exposes glTF extension data via
48/// a series of hook callbacks.
49///
50/// The type a `GltfExtensionHandler` is implemented for can define data
51/// which will be cloned for each new glTF load. This enables stateful
52/// handling of glTF extension data during a single load.
53///
54/// When loading a glTF file, a glTF object that could contain extension
55/// data will cause the relevant hook to execute once per object.
56/// Each invocation will receive all extension data, which is required because
57/// many extensions require accessing data defined by other extensions.
58///
59/// The hooks are always called once, even if there is no extension data
60/// This is useful for scenarios where additional extension data isn't
61/// required, but processing should still happen.
62pub trait GltfExtensionHandler: Send + Sync + 'static {
63/// Required for dyn cloning
64fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler>;
6566/// Called when the "global" data for an extension
67 /// at the root of a glTF file is encountered.
68#[expect(
69 unused,
70 reason = "default trait implementations do not use the arguments because they are no-ops"
71)]
72fn on_root(
73&mut self,
74 load_context: &mut LoadContext<'_>,
75 gltf: &gltf::Gltf,
76 settings: &GltfLoaderSettings,
77 ) {
78 }
7980#[cfg(feature = "bevy_animation")]
81 #[expect(
82 unused,
83 reason = "default trait implementations do not use the arguments because they are no-ops"
84)]
85/// Called when an individual animation is processed
86fn on_animation(
87&mut self,
88 load_context: &mut LoadContext<'_>,
89 gltf_animation: &gltf::Animation,
90 animation_clip: &mut AnimationClip,
91 ) {
92 }
9394#[cfg(feature = "bevy_animation")]
95 #[expect(
96 unused,
97 reason = "default trait implementations do not use the arguments because they are no-ops"
98)]
99/// Called when all animations have been collected.
100 /// `animations` is the glTF ordered list of `Handle<AnimationClip>`s
101 /// `named_animations` is a `HashMap` from animation name to `Handle<AnimationClip>`
102 /// `animation_roots` is the glTF index of the animation root object
103fn on_animations_collected(
104&mut self,
105 load_context: &mut LoadContext<'_>,
106 animations: &[Handle<AnimationClip>],
107 named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
108 animation_roots: &HashSet<usize>,
109 ) {
110 }
111112/// Called when an individual texture is processed
113#[expect(
114 unused,
115 reason = "default trait implementations do not use the arguments because they are no-ops"
116)]
117fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>) {}
118119/// Called when an individual material is processed
120#[expect(
121 unused,
122 reason = "default trait implementations do not use the arguments because they are no-ops"
123)]
124fn on_material(
125&mut self,
126 load_context: &mut LoadContext<'_>,
127 gltf_material: &gltf::Material,
128 material: Handle<GltfMaterial>,
129 material_asset: &GltfMaterial,
130 material_label: &str,
131 ) {
132 }
133134/// Called when an individual glTF primitive is processed
135 /// glTF primitives are what become a Bevy `Mesh`
136 /// This hook is useful for extensions that need to
137 /// decompress or transform primitives and their associated
138 /// glTF data.
139 ///
140 /// `buffer_data` is a reference to all of the buffers from the
141 /// glTF document, in order, after it has been loaded by Bevy. Extensions
142 /// in glTF are allowed to add arbitrary buffers, so while this
143 /// data is often vertex data, it can not be assumed to be
144 /// vertex data.
145 ///
146 /// `out_doc` is an optional `gltf::Document` which, if set,
147 /// must contain a single `gltf::Mesh` with a single
148 /// `gltf::Primitive`. This document is only used by Bevy for
149 /// the processing of the relevant primitive and can not affect
150 /// other processing.
151 ///
152 /// `out_data` is a single buffer wrapped in a `Vec`, which mirrors
153 /// the buffer structure of a loaded `gltf::Document`'s buffers, which
154 /// is the same structure as `buffer_data`. The outer `Vec` must
155 /// contain a single `Vec<u8>` of data, as only the first generated
156 /// buffer is used. If set, the loader will use this modified buffer
157 /// data instead of the original `buffer_data` to construct the Mesh.
158#[expect(
159 unused,
160 reason = "default trait implementations do not use the arguments because they are no-ops"
161)]
162fn on_gltf_primitive(
163&mut self,
164 load_context: &mut LoadContext<'_>,
165 gltf_document: &gltf::Gltf,
166 gltf_mesh: &gltf::Mesh,
167 gltf_primitive: &gltf::Primitive,
168 buffer_data: &[Vec<u8>],
169 custom_vertex_attributes: &HashMap<Box<str>, MeshVertexAttribute>,
170 gltf_mesh_on_skinned_nodes: bool,
171 gltf_mesh_on_non_skinned_nodes: bool,
172 user_mesh: &mut Option<Mesh>,
173 ) -> impl ConditionalSendFuture<Output = ()> {
174async {}
175 }
176177/// Called when an individual glTF Mesh is processed
178#[expect(
179 unused,
180 reason = "default trait implementations do not use the arguments because they are no-ops"
181)]
182fn on_gltf_mesh(
183&mut self,
184 load_context: &mut LoadContext<'_>,
185 gltf_mesh: &gltf::Mesh,
186 mesh: Handle<GltfMesh>,
187 ) {
188 }
189190/// mesh and material are spawned as a single Entity,
191 /// which means an extension would have to decide for
192 /// itself how to merge the extension data.
193#[expect(
194 unused,
195 reason = "default trait implementations do not use the arguments because they are no-ops"
196)]
197fn on_spawn_mesh_and_material(
198&mut self,
199 load_context: &mut LoadContext<'_>,
200 primitive: &gltf::Primitive,
201 mesh: &gltf::Mesh,
202 material: &gltf::Material,
203 entity: &mut EntityWorldMut,
204 material_label: &str,
205 ) {
206 }
207208/// Called when an individual Scene is done processing
209#[expect(
210 unused,
211 reason = "default trait implementations do not use the arguments because they are no-ops"
212)]
213fn on_scene_completed(
214&mut self,
215 load_context: &mut LoadContext<'_>,
216 scene: &gltf::Scene,
217 world_root_id: Entity,
218 scene_world: &mut World,
219 ) {
220 }
221222/// Called when a node is processed
223#[expect(
224 unused,
225 reason = "default trait implementations do not use the arguments because they are no-ops"
226)]
227fn on_gltf_node(
228&mut self,
229 load_context: &mut LoadContext<'_>,
230 gltf_node: &Node,
231 entity: &mut EntityWorldMut,
232 ) {
233 }
234235/// Called with a `DirectionalLight` node is spawned
236 /// which is typically created as a result of
237 /// `KHR_lights_punctual`
238#[expect(
239 unused,
240 reason = "default trait implementations do not use the arguments because they are no-ops"
241)]
242fn on_spawn_light_directional(
243&mut self,
244 load_context: &mut LoadContext<'_>,
245 gltf_node: &Node,
246 entity: &mut EntityWorldMut,
247 ) {
248 }
249/// Called with a `PointLight` node is spawned
250 /// which is typically created as a result of
251 /// `KHR_lights_punctual`
252#[expect(
253 unused,
254 reason = "default trait implementations do not use the arguments because they are no-ops"
255)]
256fn on_spawn_light_point(
257&mut self,
258 load_context: &mut LoadContext<'_>,
259 gltf_node: &Node,
260 entity: &mut EntityWorldMut,
261 ) {
262 }
263/// Called with a `SpotLight` node is spawned
264 /// which is typically created as a result of
265 /// `KHR_lights_punctual`
266#[expect(
267 unused,
268 reason = "default trait implementations do not use the arguments because they are no-ops"
269)]
270fn on_spawn_light_spot(
271&mut self,
272 load_context: &mut LoadContext<'_>,
273 gltf_node: &Node,
274 entity: &mut EntityWorldMut,
275 ) {
276 }
277}
278279/// Type-erased version of [`GltfExtensionHandler`].
280/// This is used to store heterogeneous handlers in a collection.
281pub trait ErasedGltfExtensionHandler: Send + Sync + 'static {
282/// Required for dyn cloning
283fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler>;
284285/// Called when the "global" data for an extension
286 /// at the root of a glTF file is encountered.
287fn on_root(
288&mut self,
289 load_context: &mut LoadContext<'_>,
290 gltf: &gltf::Gltf,
291 settings: &GltfLoaderSettings,
292 );
293294#[cfg(feature = "bevy_animation")]
295/// Called when an individual animation is processed
296fn on_animation(
297&mut self,
298 load_context: &mut LoadContext<'_>,
299 gltf_animation: &gltf::Animation,
300 animation_clip: &mut AnimationClip,
301 );
302303#[cfg(feature = "bevy_animation")]
304/// Called when all animations have been collected.
305fn on_animations_collected(
306&mut self,
307 load_context: &mut LoadContext<'_>,
308 animations: &[Handle<AnimationClip>],
309 named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
310 animation_roots: &HashSet<usize>,
311 );
312313/// Called when an individual texture is processed
314fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>);
315316/// Called when an individual material is processed
317fn on_material(
318&mut self,
319 load_context: &mut LoadContext<'_>,
320 gltf_material: &gltf::Material,
321 material: Handle<GltfMaterial>,
322 material_asset: &GltfMaterial,
323 material_label: &str,
324 );
325326/// Called when an individual glTF primitive is processed
327fn on_gltf_primitive<'a>(
328&'a mut self,
329 load_context: &'a mut LoadContext<'_>,
330 gltf_document: &'a gltf::Gltf,
331 gltf_mesh: &'a gltf::Mesh,
332 gltf_primitive: &'a gltf::Primitive,
333 buffer_data: &'a [Vec<u8>],
334 custom_vertex_attributes: &'a HashMap<Box<str>, MeshVertexAttribute>,
335 gltf_mesh_on_skinned_nodes: bool,
336 gltf_mesh_on_non_skinned_nodes: bool,
337 user_mesh: &'a mut Option<Mesh>,
338 ) -> BoxedFuture<'a, ()>;
339340/// Called when an individual glTF Mesh is processed
341fn on_gltf_mesh(
342&mut self,
343 load_context: &mut LoadContext<'_>,
344 gltf_mesh: &gltf::Mesh,
345 mesh: Handle<GltfMesh>,
346 );
347348/// Called when mesh and material are spawned as a single Entity
349fn on_spawn_mesh_and_material(
350&mut self,
351 load_context: &mut LoadContext<'_>,
352 primitive: &gltf::Primitive,
353 mesh: &gltf::Mesh,
354 material: &gltf::Material,
355 entity: &mut EntityWorldMut,
356 material_label: &str,
357 );
358359/// Called when an individual Scene is done processing
360fn on_scene_completed(
361&mut self,
362 load_context: &mut LoadContext<'_>,
363 scene: &gltf::Scene,
364 world_root_id: Entity,
365 scene_world: &mut World,
366 );
367368/// Called when a node is processed
369fn on_gltf_node(
370&mut self,
371 load_context: &mut LoadContext<'_>,
372 gltf_node: &Node,
373 entity: &mut EntityWorldMut,
374 );
375376/// Called when a `DirectionalLight` node is spawned
377fn on_spawn_light_directional(
378&mut self,
379 load_context: &mut LoadContext<'_>,
380 gltf_node: &Node,
381 entity: &mut EntityWorldMut,
382 );
383384/// Called when a `PointLight` node is spawned
385fn on_spawn_light_point(
386&mut self,
387 load_context: &mut LoadContext<'_>,
388 gltf_node: &Node,
389 entity: &mut EntityWorldMut,
390 );
391392/// Called when a `SpotLight` node is spawned
393fn on_spawn_light_spot(
394&mut self,
395 load_context: &mut LoadContext<'_>,
396 gltf_node: &Node,
397 entity: &mut EntityWorldMut,
398 );
399}
400401impl<H: GltfExtensionHandler> ErasedGltfExtensionHandlerfor H {
402fn dyn_clone(&self) -> Box<dyn ErasedGltfExtensionHandler> {
403Self::dyn_clone(self)
404 }
405406fn on_root(
407&mut self,
408 load_context: &mut LoadContext<'_>,
409 gltf: &gltf::Gltf,
410 settings: &GltfLoaderSettings,
411 ) {
412Self::on_root(self, load_context, gltf, settings);
413 }
414415#[cfg(feature = "bevy_animation")]
416fn on_animation(
417&mut self,
418 load_context: &mut LoadContext<'_>,
419 gltf_animation: &gltf::Animation,
420 animation_clip: &mut AnimationClip,
421 ) {
422Self::on_animation(self, load_context, gltf_animation, animation_clip);
423 }
424425#[cfg(feature = "bevy_animation")]
426fn on_animations_collected(
427&mut self,
428 load_context: &mut LoadContext<'_>,
429 animations: &[Handle<AnimationClip>],
430 named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
431 animation_roots: &HashSet<usize>,
432 ) {
433Self::on_animations_collected(
434self,
435load_context,
436animations,
437named_animations,
438animation_roots,
439 );
440 }
441442fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>) {
443Self::on_texture(self, gltf_texture, texture);
444 }
445446fn on_material(
447&mut self,
448 load_context: &mut LoadContext<'_>,
449 gltf_material: &gltf::Material,
450 material: Handle<GltfMaterial>,
451 material_asset: &GltfMaterial,
452 material_label: &str,
453 ) {
454Self::on_material(
455self,
456load_context,
457gltf_material,
458material,
459material_asset,
460material_label,
461 );
462 }
463464fn on_gltf_primitive<'a>(
465&'a mut self,
466 load_context: &'a mut LoadContext<'_>,
467 gltf_document: &'a gltf::Gltf,
468 gltf_mesh: &'a gltf::Mesh,
469 gltf_primitive: &'a gltf::Primitive,
470 buffer_data: &'a [Vec<u8>],
471 custom_vertex_attributes: &'a HashMap<Box<str>, MeshVertexAttribute>,
472 gltf_mesh_on_skinned_nodes: bool,
473 gltf_mesh_on_non_skinned_nodes: bool,
474 user_mesh: &'a mut Option<Mesh>,
475 ) -> BoxedFuture<'a, ()> {
476Box::pin(async move {
477Self::on_gltf_primitive(
478self,
479 load_context,
480 gltf_document,
481 gltf_mesh,
482 gltf_primitive,
483 buffer_data,
484 custom_vertex_attributes,
485 gltf_mesh_on_skinned_nodes,
486 gltf_mesh_on_non_skinned_nodes,
487 user_mesh,
488 )
489 .await;
490 })
491 }
492493fn on_gltf_mesh(
494&mut self,
495 load_context: &mut LoadContext<'_>,
496 gltf_mesh: &gltf::Mesh,
497 mesh: Handle<GltfMesh>,
498 ) {
499Self::on_gltf_mesh(self, load_context, gltf_mesh, mesh);
500 }
501502fn on_spawn_mesh_and_material(
503&mut self,
504 load_context: &mut LoadContext<'_>,
505 primitive: &gltf::Primitive,
506 mesh: &gltf::Mesh,
507 material: &gltf::Material,
508 entity: &mut EntityWorldMut,
509 material_label: &str,
510 ) {
511Self::on_spawn_mesh_and_material(
512self,
513load_context,
514primitive,
515mesh,
516material,
517entity,
518material_label,
519 );
520 }
521522fn on_scene_completed(
523&mut self,
524 load_context: &mut LoadContext<'_>,
525 scene: &gltf::Scene,
526 world_root_id: Entity,
527 scene_world: &mut World,
528 ) {
529Self::on_scene_completed(self, load_context, scene, world_root_id, scene_world);
530 }
531532fn on_gltf_node(
533&mut self,
534 load_context: &mut LoadContext<'_>,
535 gltf_node: &Node,
536 entity: &mut EntityWorldMut,
537 ) {
538Self::on_gltf_node(self, load_context, gltf_node, entity);
539 }
540541fn on_spawn_light_directional(
542&mut self,
543 load_context: &mut LoadContext<'_>,
544 gltf_node: &Node,
545 entity: &mut EntityWorldMut,
546 ) {
547Self::on_spawn_light_directional(self, load_context, gltf_node, entity);
548 }
549550fn on_spawn_light_point(
551&mut self,
552 load_context: &mut LoadContext<'_>,
553 gltf_node: &Node,
554 entity: &mut EntityWorldMut,
555 ) {
556Self::on_spawn_light_point(self, load_context, gltf_node, entity);
557 }
558559fn on_spawn_light_spot(
560&mut self,
561 load_context: &mut LoadContext<'_>,
562 gltf_node: &Node,
563 entity: &mut EntityWorldMut,
564 ) {
565Self::on_spawn_light_spot(self, load_context, gltf_node, entity);
566 }
567}
568569impl Clonefor Box<dyn ErasedGltfExtensionHandler> {
570fn clone(&self) -> Self {
571self.dyn_clone()
572 }
573}