Skip to main content

bevy_gltf/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![doc(
4    html_logo_url = "https://bevy.org/assets/icon.png",
5    html_favicon_url = "https://bevy.org/assets/icon.png"
6)]
7
8//! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions
9//! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy.
10//!
11//! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files.
12//!
13//! # Quick Start
14//!
15//! Here's how to spawn a simple glTF scene
16//!
17//! ```
18//! # use bevy_ecs::prelude::*;
19//! # use bevy_asset::prelude::*;
20//! # use bevy_world_serialization::prelude::*;
21//! # use bevy_transform::prelude::*;
22//! # use bevy_gltf::prelude::*;
23//!
24//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
25//!     commands.spawn((
26//!         // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"
27//!         // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.
28//!         // If this isn't specified bevy doesn't know which part of the glTF file to load.
29//!         WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
30//!         // You can use the transform to give it a position
31//!         Transform::from_xyz(2.0, 0.0, -5.0),
32//!     ));
33//! }
34//! ```
35//! # Loading parts of a glTF asset
36//!
37//! ## Using `Gltf`
38//!
39//! If you want to access part of the asset, you can load the entire `Gltf` using the `AssetServer`.
40//! Once the `Handle<Gltf>` is loaded you can then use it to access named parts of it.
41//!
42//! ```
43//! # use bevy_ecs::prelude::*;
44//! # use bevy_asset::prelude::*;
45//! # use bevy_world_serialization::prelude::*;
46//! # use bevy_transform::prelude::*;
47//! # use bevy_gltf::Gltf;
48//!
49//! // Holds the scene handle
50//! #[derive(Resource)]
51//! struct HelmetScene(Handle<Gltf>);
52//!
53//! fn load_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
54//!     let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf");
55//!     commands.insert_resource(HelmetScene(gltf));
56//! }
57//!
58//! fn spawn_gltf_objects(
59//!     mut commands: Commands,
60//!     helmet_scene: Res<HelmetScene>,
61//!     gltf_assets: Res<Assets<Gltf>>,
62//!     mut loaded: Local<bool>,
63//! ) {
64//!     // Only do this once
65//!     if *loaded {
66//!         return;
67//!     }
68//!     // Wait until the scene is loaded
69//!     let Some(gltf) = gltf_assets.get(&helmet_scene.0) else {
70//!         return;
71//!     };
72//!     *loaded = true;
73//!
74//!     // Spawns the first scene in the file
75//!     commands.spawn(WorldAssetRoot(gltf.scenes[0].clone()));
76//!
77//!     // Spawns the scene named "Lenses_low"
78//!     commands.spawn((
79//!         WorldAssetRoot(gltf.named_scenes["Lenses_low"].clone()),
80//!         Transform::from_xyz(1.0, 2.0, 3.0),
81//!     ));
82//! }
83//! ```
84//!
85//! ## Asset Labels
86//!
87//! The glTF loader let's you specify labels that let you target specific parts of the glTF.
88//!
89//! Be careful when using this feature, if you misspell a label it will simply ignore it without warning.
90//!
91//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.
92//!
93//! # Supported KHR Extensions
94//!
95//! glTF files may use functionality beyond the base glTF specification, specified as a list of
96//! required extensions. The table below shows which of the ratified Khronos extensions are
97//! supported by Bevy.
98//!
99//! | Extension                         | Supported | Requires feature                    |
100//! | --------------------------------- | --------- | ----------------------------------- |
101//! | `KHR_animation_pointer`           | ❌        |                                     |
102//! | `KHR_draco_mesh_compression`      | ❌        |                                     |
103//! | `KHR_lights_punctual`             | ✅        |                                     |
104//! | `KHR_materials_anisotropy`        | ✅        | `pbr_anisotropy_texture`            |
105//! | `KHR_materials_clearcoat`         | ✅        | `pbr_multi_layer_material_textures` |
106//! | `KHR_materials_dispersion`        | ❌        |                                     |
107//! | `KHR_materials_emissive_strength` | ✅        |                                     |
108//! | `KHR_materials_ior`               | ✅        |                                     |
109//! | `KHR_materials_iridescence`       | ❌        |                                     |
110//! | `KHR_materials_sheen`             | ❌        |                                     |
111//! | `KHR_materials_specular`          | ✅        | `pbr_specular_textures`             |
112//! | `KHR_materials_transmission`      | ✅        | `pbr_transmission_textures`         |
113//! | `KHR_materials_unlit`             | ✅        |                                     |
114//! | `KHR_materials_variants`          | ❌        |                                     |
115//! | `KHR_materials_volume`            | ✅        |                                     |
116//! | `KHR_mesh_quantization`           | ❌        |                                     |
117//! | `KHR_texture_basisu`              | ❌\*      |                                     |
118//! | `KHR_texture_transform`           | ✅\**     |                                     |
119//! | `KHR_xmp_json_ld`                 | ❌        |                                     |
120//! | `EXT_mesh_gpu_instancing`         | ❌        |                                     |
121//! | `EXT_meshopt_compression`         | ❌        |                                     |
122//! | `EXT_texture_webp`                | ❌\*      |                                     |
123//!
124//! \*Bevy supports ktx2 and webp formats but doesn't support the extension's syntax, see [#19104](https://github.com/bevyengine/bevy/issues/19104).
125//!
126//! \**`KHR_texture_transform` is only supported on `base_color_texture`, see [#15310](https://github.com/bevyengine/bevy/issues/15310).
127//!
128//! See the [glTF Extension Registry](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md) for more information on extensions.
129
130mod assets;
131pub mod convert_coordinates;
132mod label;
133mod loader;
134mod material;
135/// A set of utilities for accessing and converting vertex attribute data
136pub mod vertex_attributes;
137
138extern crate alloc;
139
140use alloc::sync::Arc;
141use serde::{Deserialize, Serialize};
142use std::sync::Mutex;
143use tracing::warn;
144
145use bevy_platform::collections::HashMap;
146
147use bevy_app::prelude::*;
148use bevy_asset::AssetApp;
149use bevy_ecs::prelude::Resource;
150use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};
151use bevy_mesh::MeshVertexAttribute;
152
153/// The glTF prelude.
154///
155/// This includes the most common types in this crate, re-exported for your convenience.
156pub mod prelude {
157    #[doc(hidden)]
158    pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
159}
160
161use crate::{convert_coordinates::GltfConvertCoordinates, extensions::GltfExtensionHandlers};
162
163pub use {assets::*, label::GltfAssetLabel, loader::*, material::GltfMaterial};
164
165/// Re-exports for GLTF
166pub mod gltf {
167    #[doc(hidden)]
168    pub use gltf::{Animation, Document, Gltf, Material, Mesh, Primitive, Scene, Texture};
169}
170
171// Has to store an Arc<Mutex<...>> as there is no other way to mutate fields of asset loaders.
172/// Stores default [`ImageSamplerDescriptor`] in main world.
173#[derive(impl bevy_ecs::resource::Resource for DefaultGltfImageSampler where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
174pub struct DefaultGltfImageSampler(Arc<Mutex<ImageSamplerDescriptor>>);
175
176impl DefaultGltfImageSampler {
177    /// Creates a new [`DefaultGltfImageSampler`].
178    pub fn new(descriptor: &ImageSamplerDescriptor) -> Self {
179        Self(Arc::new(Mutex::new(descriptor.clone())))
180    }
181
182    /// Returns the current default [`ImageSamplerDescriptor`].
183    pub fn get(&self) -> ImageSamplerDescriptor {
184        self.0.lock().unwrap().clone()
185    }
186
187    /// Makes a clone of internal [`Arc`] pointer.
188    ///
189    /// Intended only to be used by code with no access to ECS.
190    pub fn get_internal(&self) -> Arc<Mutex<ImageSamplerDescriptor>> {
191        self.0.clone()
192    }
193
194    /// Replaces default [`ImageSamplerDescriptor`].
195    ///
196    /// Doesn't apply to samplers already built on top of it, i.e. `GltfLoader`'s output.
197    /// Assets need to manually be reloaded.
198    pub fn set(&self, descriptor: &ImageSamplerDescriptor) {
199        *self.0.lock().unwrap() = descriptor.clone();
200    }
201}
202
203/// Controls the bounds related components that are assigned to skinned mesh
204/// entities. These components are used by systems like frustum culling.
205#[derive(#[automatically_derived]
impl ::core::default::Default for GltfSkinnedMeshBoundsPolicy {
    #[inline]
    fn default() -> GltfSkinnedMeshBoundsPolicy { Self::Dynamic }
}Default, #[automatically_derived]
impl ::core::marker::Copy for GltfSkinnedMeshBoundsPolicy { }Copy, #[automatically_derived]
impl ::core::clone::Clone for GltfSkinnedMeshBoundsPolicy {
    #[inline]
    fn clone(&self) -> GltfSkinnedMeshBoundsPolicy { *self }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GltfSkinnedMeshBoundsPolicy {
    #[inline]
    fn eq(&self, other: &GltfSkinnedMeshBoundsPolicy) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl _serde::Serialize for GltfSkinnedMeshBoundsPolicy {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private228::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                match *self {
                    GltfSkinnedMeshBoundsPolicy::BindPose =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "GltfSkinnedMeshBoundsPolicy", 0u32, "BindPose"),
                    GltfSkinnedMeshBoundsPolicy::Dynamic =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "GltfSkinnedMeshBoundsPolicy", 1u32, "Dynamic"),
                    GltfSkinnedMeshBoundsPolicy::NoFrustumCulling =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "GltfSkinnedMeshBoundsPolicy", 2u32, "NoFrustumCulling"),
                }
            }
        }
    };Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl<'de> _serde::Deserialize<'de> for GltfSkinnedMeshBoundsPolicy {
            fn deserialize<__D>(__deserializer: __D)
                -> _serde::__private228::Result<Self, __D::Error> where
                __D: _serde::Deserializer<'de> {
                #[allow(non_camel_case_types)]
                #[doc(hidden)]
                enum __Field { __field0, __field1, __field2, }
                #[doc(hidden)]
                struct __FieldVisitor;
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                    type Value = __Field;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "variant identifier")
                    }
                    fn visit_u64<__E>(self, __value: u64)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            0u64 => _serde::__private228::Ok(__Field::__field0),
                            1u64 => _serde::__private228::Ok(__Field::__field1),
                            2u64 => _serde::__private228::Ok(__Field::__field2),
                            _ =>
                                _serde::__private228::Err(_serde::de::Error::invalid_value(_serde::de::Unexpected::Unsigned(__value),
                                        &"variant index 0 <= i < 3")),
                        }
                    }
                    fn visit_str<__E>(self, __value: &str)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            "BindPose" => _serde::__private228::Ok(__Field::__field0),
                            "Dynamic" => _serde::__private228::Ok(__Field::__field1),
                            "NoFrustumCulling" =>
                                _serde::__private228::Ok(__Field::__field2),
                            _ => {
                                _serde::__private228::Err(_serde::de::Error::unknown_variant(__value,
                                        VARIANTS))
                            }
                        }
                    }
                    fn visit_bytes<__E>(self, __value: &[u8])
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            b"BindPose" => _serde::__private228::Ok(__Field::__field0),
                            b"Dynamic" => _serde::__private228::Ok(__Field::__field1),
                            b"NoFrustumCulling" =>
                                _serde::__private228::Ok(__Field::__field2),
                            _ => {
                                let __value =
                                    &_serde::__private228::from_utf8_lossy(__value);
                                _serde::__private228::Err(_serde::de::Error::unknown_variant(__value,
                                        VARIANTS))
                            }
                        }
                    }
                }
                #[automatically_derived]
                impl<'de> _serde::Deserialize<'de> for __Field {
                    #[inline]
                    fn deserialize<__D>(__deserializer: __D)
                        -> _serde::__private228::Result<Self, __D::Error> where
                        __D: _serde::Deserializer<'de> {
                        _serde::Deserializer::deserialize_identifier(__deserializer,
                            __FieldVisitor)
                    }
                }
                #[doc(hidden)]
                struct __Visitor<'de> {
                    marker: _serde::__private228::PhantomData<GltfSkinnedMeshBoundsPolicy>,
                    lifetime: _serde::__private228::PhantomData<&'de ()>,
                }
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                    type Value = GltfSkinnedMeshBoundsPolicy;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "enum GltfSkinnedMeshBoundsPolicy")
                    }
                    fn visit_enum<__A>(self, __data: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::EnumAccess<'de> {
                        match _serde::de::EnumAccess::variant(__data)? {
                            (__Field::__field0, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(GltfSkinnedMeshBoundsPolicy::BindPose)
                            }
                            (__Field::__field1, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(GltfSkinnedMeshBoundsPolicy::Dynamic)
                            }
                            (__Field::__field2, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(GltfSkinnedMeshBoundsPolicy::NoFrustumCulling)
                            }
                        }
                    }
                }
                #[doc(hidden)]
                const VARIANTS: &'static [&'static str] =
                    &["BindPose", "Dynamic", "NoFrustumCulling"];
                _serde::Deserializer::deserialize_enum(__deserializer,
                    "GltfSkinnedMeshBoundsPolicy", VARIANTS,
                    __Visitor {
                        marker: _serde::__private228::PhantomData::<GltfSkinnedMeshBoundsPolicy>,
                        lifetime: _serde::__private228::PhantomData,
                    })
            }
        }
    };Deserialize)]
206pub enum GltfSkinnedMeshBoundsPolicy {
207    /// Skinned meshes are assigned an `Aabb` component calculated from the bind
208    /// pose `Mesh`.
209    BindPose,
210    /// Skinned meshes are created with [`SkinnedMeshBounds`](bevy_mesh::skinning::SkinnedMeshBounds)
211    /// and assigned a [`DynamicSkinnedMeshBounds`](bevy_camera::visibility::DynamicSkinnedMeshBounds)
212    /// component. See `DynamicSkinnedMeshBounds` for details.
213    #[default]
214    Dynamic,
215    /// Same as `BindPose`, but also assign a `NoFrustumCulling` component. That
216    /// component tells the `bevy_camera` plugin to avoid frustum culling the
217    /// skinned mesh.
218    NoFrustumCulling,
219}
220
221/// Adds support for glTF file loading to the app.
222pub struct GltfPlugin {
223    /// The default image sampler to lay glTF sampler data on top of.
224    ///
225    /// Can be modified with the [`DefaultGltfImageSampler`] resource.
226    pub default_sampler: ImageSamplerDescriptor,
227
228    /// The default glTF coordinate conversion setting. This can be overridden
229    /// per-load by [`GltfLoaderSettings::convert_coordinates`].
230    pub convert_coordinates: GltfConvertCoordinates,
231
232    /// Registry for custom vertex attributes.
233    ///
234    /// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
235    pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
236
237    /// The default policy for skinned mesh bounds. Can be overridden by
238    /// [`GltfLoaderSettings::skinned_mesh_bounds_policy`].
239    pub skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy,
240}
241
242impl Default for GltfPlugin {
243    fn default() -> Self {
244        GltfPlugin {
245            default_sampler: ImageSamplerDescriptor::linear(),
246            custom_vertex_attributes: HashMap::default(),
247            convert_coordinates: GltfConvertCoordinates::default(),
248            skinned_mesh_bounds_policy: Default::default(),
249        }
250    }
251}
252
253impl GltfPlugin {
254    /// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].
255    ///
256    /// `name` must be the attribute name as found in the glTF data, which must start with an underscore.
257    /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
258    /// for additional details on custom attributes.
259    pub fn add_custom_vertex_attribute(
260        mut self,
261        name: &str,
262        attribute: MeshVertexAttribute,
263    ) -> Self {
264        self.custom_vertex_attributes.insert(name.into(), attribute);
265        self
266    }
267}
268
269impl Plugin for GltfPlugin {
270    fn build(&self, app: &mut App) {
271        app.init_asset::<Gltf>()
272            .init_asset::<GltfNode>()
273            .init_asset::<GltfPrimitive>()
274            .init_asset::<GltfMesh>()
275            .init_asset::<GltfSkin>()
276            .init_asset::<GltfMaterial>()
277            .preregister_asset_loader::<GltfLoader>(&["gltf", "glb"])
278            .init_resource::<GltfExtensionHandlers>();
279    }
280
281    fn finish(&self, app: &mut App) {
282        let supported_compressed_formats = if let Some(resource) =
283            app.world().get_resource::<CompressedImageFormatSupport>()
284        {
285            resource.0
286        } else {
287            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event src/lib.rs:287",
                        "bevy_gltf", ::tracing::Level::WARN,
                        ::tracing_core::__macro_support::Option::Some("src/lib.rs"),
                        ::tracing_core::__macro_support::Option::Some(287u32),
                        ::tracing_core::__macro_support::Option::Some("bevy_gltf"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::WARN <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::WARN <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \
288            RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");
289            CompressedImageFormats::NONE
290        };
291
292        let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
293        let default_sampler = default_sampler_resource.get_internal();
294        app.insert_resource(default_sampler_resource);
295
296        let extensions = app.world().resource::<GltfExtensionHandlers>();
297
298        app.register_asset_loader(GltfLoader {
299            supported_compressed_formats,
300            custom_vertex_attributes: self.custom_vertex_attributes.clone(),
301            default_sampler,
302            default_convert_coordinates: self.convert_coordinates,
303            extensions: extensions.0.clone(),
304            default_skinned_mesh_bounds_policy: self.skinned_mesh_bounds_policy,
305        });
306    }
307}