#![cfg_attr(docsrs, feature(doc_cfg))]1#![forbid(unsafe_code)]2#![doc(3html_logo_url = "https://bevy.org/assets/icon.png",4html_favicon_url = "https://bevy.org/assets/icon.png"5)]67//! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions8//! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy.9//!10//! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files.11//!12//! # Quick Start13//!14//! Here's how to spawn a simple glTF scene15//!16//! ```17//! # use bevy_ecs::prelude::*;18//! # use bevy_asset::prelude::*;19//! # use bevy_scene::prelude::*;20//! # use bevy_transform::prelude::*;21//! # use bevy_gltf::prelude::*;22//!23//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {24//! commands.spawn((25//! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"26//! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.27//! // If this isn't specified bevy doesn't know which part of the glTF file to load.28//! SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),29//! // You can use the transform to give it a position30//! Transform::from_xyz(2.0, 0.0, -5.0),31//! ));32//! }33//! ```34//! # Loading parts of a glTF asset35//!36//! ## Using `Gltf`37//!38//! If you want to access part of the asset, you can load the entire `Gltf` using the `AssetServer`.39//! Once the `Handle<Gltf>` is loaded you can then use it to access named parts of it.40//!41//! ```42//! # use bevy_ecs::prelude::*;43//! # use bevy_asset::prelude::*;44//! # use bevy_scene::prelude::*;45//! # use bevy_transform::prelude::*;46//! # use bevy_gltf::Gltf;47//!48//! // Holds the scene handle49//! #[derive(Resource)]50//! struct HelmetScene(Handle<Gltf>);51//!52//! fn load_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {53//! let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf");54//! commands.insert_resource(HelmetScene(gltf));55//! }56//!57//! fn spawn_gltf_objects(58//! mut commands: Commands,59//! helmet_scene: Res<HelmetScene>,60//! gltf_assets: Res<Assets<Gltf>>,61//! mut loaded: Local<bool>,62//! ) {63//! // Only do this once64//! if *loaded {65//! return;66//! }67//! // Wait until the scene is loaded68//! let Some(gltf) = gltf_assets.get(&helmet_scene.0) else {69//! return;70//! };71//! *loaded = true;72//!73//! // Spawns the first scene in the file74//! commands.spawn(SceneRoot(gltf.scenes[0].clone()));75//!76//! // Spawns the scene named "Lenses_low"77//! commands.spawn((78//! SceneRoot(gltf.named_scenes["Lenses_low"].clone()),79//! Transform::from_xyz(1.0, 2.0, 3.0),80//! ));81//! }82//! ```83//!84//! ## Asset Labels85//!86//! The glTF loader let's you specify labels that let you target specific parts of the glTF.87//!88//! Be careful when using this feature, if you misspell a label it will simply ignore it without warning.89//!90//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.91//!92//! # Supported KHR Extensions93//!94//! glTF files may use functionality beyond the base glTF specification, specified as a list of95//! required extensions. The table below shows which of the ratified Khronos extensions are96//! supported by Bevy.97//!98//! | Extension | Supported | Requires feature |99//! | --------------------------------- | --------- | ----------------------------------- |100//! | `KHR_animation_pointer` | ❌ | |101//! | `KHR_draco_mesh_compression` | ❌ | |102//! | `KHR_lights_punctual` | ✅ | |103//! | `KHR_materials_anisotropy` | ✅ | `pbr_anisotropy_texture` |104//! | `KHR_materials_clearcoat` | ✅ | `pbr_multi_layer_material_textures` |105//! | `KHR_materials_dispersion` | ❌ | |106//! | `KHR_materials_emissive_strength` | ✅ | |107//! | `KHR_materials_ior` | ✅ | |108//! | `KHR_materials_iridescence` | ❌ | |109//! | `KHR_materials_sheen` | ❌ | |110//! | `KHR_materials_specular` | ✅ | `pbr_specular_textures` |111//! | `KHR_materials_transmission` | ✅ | `pbr_transmission_textures` |112//! | `KHR_materials_unlit` | ✅ | |113//! | `KHR_materials_variants` | ❌ | |114//! | `KHR_materials_volume` | ✅ | |115//! | `KHR_mesh_quantization` | ❌ | |116//! | `KHR_texture_basisu` | ❌\* | |117//! | `KHR_texture_transform` | ✅\** | |118//! | `KHR_xmp_json_ld` | ❌ | |119//! | `EXT_mesh_gpu_instancing` | ❌ | |120//! | `EXT_meshopt_compression` | ❌ | |121//! | `EXT_texture_webp` | ❌\* | |122//!123//! \*Bevy supports ktx2 and webp formats but doesn't support the extension's syntax, see [#19104](https://github.com/bevyengine/bevy/issues/19104).124//!125//! \**`KHR_texture_transform` is only supported on `base_color_texture`, see [#15310](https://github.com/bevyengine/bevy/issues/15310).126//!127//! See the [glTF Extension Registry](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md) for more information on extensions.128129mod assets;130pub mod convert_coordinates;131mod label;132mod loader;133mod material;134mod vertex_attributes;135136extern crate alloc;137138use alloc::sync::Arc;139use serde::{Deserialize, Serialize};140use std::sync::Mutex;141use tracing::warn;142143use bevy_platform::collections::HashMap;144145use bevy_app::prelude::*;146use bevy_asset::AssetApp;147use bevy_ecs::prelude::Resource;148use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};149use bevy_mesh::MeshVertexAttribute;150151/// The glTF prelude.152///153/// This includes the most common types in this crate, re-exported for your convenience.154pub mod prelude {155#[doc(hidden)]156pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};157}158159use crate::{convert_coordinates::GltfConvertCoordinates, extensions::GltfExtensionHandlers};160161pub use {assets::*, label::GltfAssetLabel, loader::*, material::GltfMaterial};162163/// Re-exports for GLTF164pub mod gltf {165#[doc(hidden)]166pub use gltf::{Animation, Gltf, Material, Mesh, Primitive, Scene, Texture};167}168169// Has to store an Arc<Mutex<...>> as there is no other way to mutate fields of asset loaders.170/// Stores default [`ImageSamplerDescriptor`] in main world.171#[derive(Resource)]172pub struct DefaultGltfImageSampler(Arc<Mutex<ImageSamplerDescriptor>>);173174impl DefaultGltfImageSampler {175/// Creates a new [`DefaultGltfImageSampler`].176pub fn new(descriptor: &ImageSamplerDescriptor) -> Self {177Self(Arc::new(Mutex::new(descriptor.clone())))178}179180/// Returns the current default [`ImageSamplerDescriptor`].181pub fn get(&self) -> ImageSamplerDescriptor {182self.0.lock().unwrap().clone()183}184185/// Makes a clone of internal [`Arc`] pointer.186///187/// Intended only to be used by code with no access to ECS.188pub fn get_internal(&self) -> Arc<Mutex<ImageSamplerDescriptor>> {189self.0.clone()190}191192/// Replaces default [`ImageSamplerDescriptor`].193///194/// Doesn't apply to samplers already built on top of it, i.e. `GltfLoader`'s output.195/// Assets need to manually be reloaded.196pub fn set(&self, descriptor: &ImageSamplerDescriptor) {197*self.0.lock().unwrap() = descriptor.clone();198}199}200201/// Controls the bounds related components that are assigned to skinned mesh202/// entities. These components are used by systems like frustum culling.203#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)]204pub enum GltfSkinnedMeshBoundsPolicy {205/// Skinned meshes are assigned an `Aabb` component calculated from the bind206/// pose `Mesh`.207BindPose,208/// Skinned meshes are created with [`SkinnedMeshBounds`](bevy_mesh::skinning::SkinnedMeshBounds)209/// and assigned a [`DynamicSkinnedMeshBounds`](bevy_camera::visibility::DynamicSkinnedMeshBounds)210/// component. See `DynamicSkinnedMeshBounds` for details.211#[default]212Dynamic,213/// Same as `BindPose`, but also assign a `NoFrustumCulling` component. That214/// component tells the `bevy_camera` plugin to avoid frustum culling the215/// skinned mesh.216NoFrustumCulling,217}218219/// Adds support for glTF file loading to the app.220pub struct GltfPlugin {221/// The default image sampler to lay glTF sampler data on top of.222///223/// Can be modified with the [`DefaultGltfImageSampler`] resource.224pub default_sampler: ImageSamplerDescriptor,225226/// The default glTF coordinate conversion setting. This can be overridden227/// per-load by [`GltfLoaderSettings::convert_coordinates`].228pub convert_coordinates: GltfConvertCoordinates,229230/// Registry for custom vertex attributes.231///232/// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].233pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,234235/// The default policy for skinned mesh bounds. Can be overridden by236/// [`GltfLoaderSettings::skinned_mesh_bounds_policy`].237pub skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy,238}239240impl Default for GltfPlugin {241fn default() -> Self {242GltfPlugin {243default_sampler: ImageSamplerDescriptor::linear(),244custom_vertex_attributes: HashMap::default(),245convert_coordinates: GltfConvertCoordinates::default(),246skinned_mesh_bounds_policy: Default::default(),247}248}249}250251impl GltfPlugin {252/// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].253///254/// `name` must be the attribute name as found in the glTF data, which must start with an underscore.255/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)256/// for additional details on custom attributes.257pub fn add_custom_vertex_attribute(258mut self,259name: &str,260attribute: MeshVertexAttribute,261) -> Self {262self.custom_vertex_attributes.insert(name.into(), attribute);263self264}265}266267impl Plugin for GltfPlugin {268fn build(&self, app: &mut App) {269app.init_asset::<Gltf>()270.init_asset::<GltfNode>()271.init_asset::<GltfPrimitive>()272.init_asset::<GltfMesh>()273.init_asset::<GltfSkin>()274.init_asset::<GltfMaterial>()275.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"])276.init_resource::<GltfExtensionHandlers>();277}278279fn finish(&self, app: &mut App) {280let supported_compressed_formats = if let Some(resource) =281app.world().get_resource::<CompressedImageFormatSupport>()282{283resource.0284} else {285warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \286RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");287CompressedImageFormats::NONE288};289290let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);291let default_sampler = default_sampler_resource.get_internal();292app.insert_resource(default_sampler_resource);293294let extensions = app.world().resource::<GltfExtensionHandlers>();295296app.register_asset_loader(GltfLoader {297supported_compressed_formats,298custom_vertex_attributes: self.custom_vertex_attributes.clone(),299default_sampler,300default_convert_coordinates: self.convert_coordinates,301extensions: extensions.0.clone(),302default_skinned_mesh_bounds_policy: self.skinned_mesh_bounds_policy,303});304}305}306307308