bevy_gltf/loader/extensions/
mod.rs

1//! glTF extensions defined by the Khronos Group and other vendors
2
3mod khr_materials_anisotropy;
4mod khr_materials_clearcoat;
5mod khr_materials_specular;
6
7use alloc::sync::Arc;
8use async_lock::RwLock;
9
10use bevy_asset::{Handle, LoadContext};
11use bevy_ecs::{
12    entity::Entity,
13    resource::Resource,
14    world::{EntityWorldMut, World},
15};
16use bevy_pbr::StandardMaterial;
17use gltf::Node;
18
19#[cfg(feature = "bevy_animation")]
20use {
21    bevy_animation::AnimationClip,
22    bevy_platform::collections::{HashMap, HashSet},
23};
24
25use crate::GltfMesh;
26
27pub(crate) use self::{
28    khr_materials_anisotropy::AnisotropyExtension, khr_materials_clearcoat::ClearcoatExtension,
29    khr_materials_specular::SpecularExtension,
30};
31
32/// Stores the `GltfExtensionHandler` implementations so that they
33/// can be added by users and also passed to the glTF loader
34#[derive(Resource, Default)]
35pub struct GltfExtensionHandlers(pub Arc<RwLock<Vec<Box<dyn GltfExtensionHandler>>>>);
36
37/// glTF Extensions can attach data to any objects in a glTF file.
38/// This is done by inserting data in the `extensions` sub-object, and
39/// data in the extensions sub-object is keyed by the id of the extension.
40/// For example: `KHR_materials_variants`, `EXT_meshopt_compression`, or `BEVY_my_tool`
41///
42/// A list of publicly known extensions and their ids can be found
43/// in the [KhronosGroup/glTF](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md)
44/// git repo. Vendors reserve prefixes, such as the `BEVY` prefix,
45/// which is also listed in the [KhronosGroup repo](https://github.com/KhronosGroup/glTF/blob/main/extensions/Prefixes.md).
46///
47/// The `GltfExtensionHandler` trait should be implemented to participate in
48/// processing glTF files as they load, and exposes glTF extension data via
49/// a series of hook callbacks.
50///
51/// The type a `GltfExtensionHandler` is implemented for can define data
52/// which will be cloned for each new glTF load. This enables stateful
53/// handling of glTF extension data during a single load.
54///
55/// When loading a glTF file, a glTF object that could contain extension
56/// data will cause the relevant hook to execute once per object.
57/// Each invocation will receive all extension data, which is required because
58/// many extensions require accessing data defined by other extensions.
59///
60/// The hooks are always called once, even if there is no extension data
61/// This is useful for scenarios where additional extension data isn't
62/// required, but processing should still happen.
63pub trait GltfExtensionHandler: Send + Sync {
64    /// Required for dyn cloning
65    fn dyn_clone(&self) -> Box<dyn GltfExtensionHandler>;
66
67    /// Called when the "global" data for an extension
68    /// at the root of a glTF file is encountered.
69    #[expect(
70        unused,
71        reason = "default trait implementations do not use the arguments because they are no-ops"
72    )]
73    fn on_root(&mut self, gltf: &gltf::Gltf) {}
74
75    #[cfg(feature = "bevy_animation")]
76    #[expect(
77        unused,
78        reason = "default trait implementations do not use the arguments because they are no-ops"
79    )]
80    /// Called when an individual animation is processed
81    fn on_animation(&mut self, gltf_animation: &gltf::Animation, handle: Handle<AnimationClip>) {}
82
83    #[cfg(feature = "bevy_animation")]
84    #[expect(
85        unused,
86        reason = "default trait implementations do not use the arguments because they are no-ops"
87    )]
88    /// Called when all animations have been collected.
89    /// `animations` is the glTF ordered list of `Handle<AnimationClip>`s
90    /// `named_animations` is a `HashMap` from animation name to `Handle<AnimationClip>`
91    /// `animation_roots` is the glTF index of the animation root object
92    fn on_animations_collected(
93        &mut self,
94        load_context: &mut LoadContext<'_>,
95        animations: &[Handle<AnimationClip>],
96        named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
97        animation_roots: &HashSet<usize>,
98    ) {
99    }
100
101    /// Called when an individual texture is processed
102    #[expect(
103        unused,
104        reason = "default trait implementations do not use the arguments because they are no-ops"
105    )]
106    fn on_texture(&mut self, gltf_texture: &gltf::Texture, texture: Handle<bevy_image::Image>) {}
107
108    /// Called when an individual material is processed
109    #[expect(
110        unused,
111        reason = "default trait implementations do not use the arguments because they are no-ops"
112    )]
113    fn on_material(
114        &mut self,
115        load_context: &mut LoadContext<'_>,
116        gltf_material: &gltf::Material,
117        material: Handle<StandardMaterial>,
118    ) {
119    }
120
121    /// Called when an individual glTF Mesh is processed
122    #[expect(
123        unused,
124        reason = "default trait implementations do not use the arguments because they are no-ops"
125    )]
126    fn on_gltf_mesh(
127        &mut self,
128        load_context: &mut LoadContext<'_>,
129        gltf_mesh: &gltf::Mesh,
130        mesh: Handle<GltfMesh>,
131    ) {
132    }
133
134    /// mesh and material are spawned as a single Entity,
135    /// which means an extension would have to decide for
136    /// itself how to merge the extension data.
137    #[expect(
138        unused,
139        reason = "default trait implementations do not use the arguments because they are no-ops"
140    )]
141    fn on_spawn_mesh_and_material(
142        &mut self,
143        load_context: &mut LoadContext<'_>,
144        primitive: &gltf::Primitive,
145        mesh: &gltf::Mesh,
146        material: &gltf::Material,
147        entity: &mut EntityWorldMut,
148    ) {
149    }
150
151    /// Called when an individual Scene is done processing
152    #[expect(
153        unused,
154        reason = "default trait implementations do not use the arguments because they are no-ops"
155    )]
156    fn on_scene_completed(
157        &mut self,
158        load_context: &mut LoadContext<'_>,
159        scene: &gltf::Scene,
160        world_root_id: Entity,
161        scene_world: &mut World,
162    ) {
163    }
164
165    /// Called when a node is processed
166    #[expect(
167        unused,
168        reason = "default trait implementations do not use the arguments because they are no-ops"
169    )]
170    fn on_gltf_node(
171        &mut self,
172        load_context: &mut LoadContext<'_>,
173        gltf_node: &Node,
174        entity: &mut EntityWorldMut,
175    ) {
176    }
177
178    /// Called with a `DirectionalLight` node is spawned
179    /// which is typically created as a result of
180    /// `KHR_lights_punctual`
181    #[expect(
182        unused,
183        reason = "default trait implementations do not use the arguments because they are no-ops"
184    )]
185    fn on_spawn_light_directional(
186        &mut self,
187        load_context: &mut LoadContext<'_>,
188        gltf_node: &Node,
189        entity: &mut EntityWorldMut,
190    ) {
191    }
192    /// Called with a `PointLight` node is spawned
193    /// which is typically created as a result of
194    /// `KHR_lights_punctual`
195    #[expect(
196        unused,
197        reason = "default trait implementations do not use the arguments because they are no-ops"
198    )]
199    fn on_spawn_light_point(
200        &mut self,
201        load_context: &mut LoadContext<'_>,
202        gltf_node: &Node,
203        entity: &mut EntityWorldMut,
204    ) {
205    }
206    /// Called with a `SpotLight` node is spawned
207    /// which is typically created as a result of
208    /// `KHR_lights_punctual`
209    #[expect(
210        unused,
211        reason = "default trait implementations do not use the arguments because they are no-ops"
212    )]
213    fn on_spawn_light_spot(
214        &mut self,
215        load_context: &mut LoadContext<'_>,
216        gltf_node: &Node,
217        entity: &mut EntityWorldMut,
218    ) {
219    }
220}
221
222impl Clone for Box<dyn GltfExtensionHandler> {
223    fn clone(&self) -> Self {
224        self.dyn_clone()
225    }
226}