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)]
78//! 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.
129130mod 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;
137138extern crate alloc;
139140use alloc::sync::Arc;
141use serde::{Deserialize, Serialize};
142use std::sync::Mutex;
143use tracing::warn;
144145use bevy_platform::collections::HashMap;
146147use bevy_app::prelude::*;
148use bevy_asset::AssetApp;
149use bevy_ecs::prelude::Resource;
150use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};
151use bevy_mesh::MeshVertexAttribute;
152153/// 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)]
158pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
159}
160161use crate::{convert_coordinates::GltfConvertCoordinates, extensions::GltfExtensionHandlers};
162163pub use {assets::*, label::GltfAssetLabel, loader::*, material::GltfMaterial};
164165/// Re-exports for GLTF
166pub mod gltf {
167#[doc(hidden)]
168pub use gltf::{Animation, Document, Gltf, Material, Mesh, Primitive, Scene, Texture};
169}
170171// 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>>);
175176impl DefaultGltfImageSampler {
177/// Creates a new [`DefaultGltfImageSampler`].
178pub fn new(descriptor: &ImageSamplerDescriptor) -> Self {
179Self(Arc::new(Mutex::new(descriptor.clone())))
180 }
181182/// Returns the current default [`ImageSamplerDescriptor`].
183pub fn get(&self) -> ImageSamplerDescriptor {
184self.0.lock().unwrap().clone()
185 }
186187/// Makes a clone of internal [`Arc`] pointer.
188 ///
189 /// Intended only to be used by code with no access to ECS.
190pub fn get_internal(&self) -> Arc<Mutex<ImageSamplerDescriptor>> {
191self.0.clone()
192 }
193194/// 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.
198pub fn set(&self, descriptor: &ImageSamplerDescriptor) {
199*self.0.lock().unwrap() = descriptor.clone();
200 }
201}
202203/// 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`.
209BindPose,
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]
214Dynamic,
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.
218NoFrustumCulling,
219}
220221/// 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.
226pub default_sampler: ImageSamplerDescriptor,
227228/// The default glTF coordinate conversion setting. This can be overridden
229 /// per-load by [`GltfLoaderSettings::convert_coordinates`].
230pub convert_coordinates: GltfConvertCoordinates,
231232/// Registry for custom vertex attributes.
233 ///
234 /// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
235pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
236237/// The default policy for skinned mesh bounds. Can be overridden by
238 /// [`GltfLoaderSettings::skinned_mesh_bounds_policy`].
239pub skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy,
240}
241242impl Defaultfor GltfPlugin {
243fn default() -> Self {
244GltfPlugin {
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}
252253impl 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.
259pub fn add_custom_vertex_attribute(
260mut self,
261 name: &str,
262 attribute: MeshVertexAttribute,
263 ) -> Self {
264self.custom_vertex_attributes.insert(name.into(), attribute);
265self266 }
267}
268269impl Pluginfor GltfPlugin {
270fn build(&self, app: &mut App) {
271app.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 }
280281fn finish(&self, app: &mut App) {
282let supported_compressed_formats = if let Some(resource) =
283app.world().get_resource::<CompressedImageFormatSupport>()
284 {
285resource.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.");
289CompressedImageFormats::NONE
290 };
291292let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
293let default_sampler = default_sampler_resource.get_internal();
294app.insert_resource(default_sampler_resource);
295296let extensions = app.world().resource::<GltfExtensionHandlers>();
297298app.register_asset_loader(GltfLoader {
299supported_compressed_formats,
300 custom_vertex_attributes: self.custom_vertex_attributes.clone(),
301default_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}