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_scene::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//!         SceneRoot(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_scene::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(SceneRoot(gltf.scenes[0].clone()));
76//!
77//!     // Spawns the scene named "Lenses_low"
78//!     commands.spawn((
79//!         SceneRoot(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
93mod assets;
94mod convert_coordinates;
95mod label;
96mod loader;
97mod vertex_attributes;
98
99extern crate alloc;
100
101use alloc::sync::Arc;
102use std::sync::Mutex;
103use tracing::warn;
104
105use bevy_platform::collections::HashMap;
106
107use bevy_app::prelude::*;
108use bevy_asset::AssetApp;
109use bevy_ecs::prelude::Resource;
110use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};
111use bevy_mesh::MeshVertexAttribute;
112
113/// The glTF prelude.
114///
115/// This includes the most common types in this crate, re-exported for your convenience.
116pub mod prelude {
117    #[doc(hidden)]
118    pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
119}
120
121pub use {assets::*, label::GltfAssetLabel, loader::*};
122
123// Has to store an Arc<Mutex<...>> as there is no other way to mutate fields of asset loaders.
124/// Stores default [`ImageSamplerDescriptor`] in main world.
125#[derive(Resource)]
126pub struct DefaultGltfImageSampler(Arc<Mutex<ImageSamplerDescriptor>>);
127
128impl DefaultGltfImageSampler {
129    /// Creates a new [`DefaultGltfImageSampler`].
130    pub fn new(descriptor: &ImageSamplerDescriptor) -> Self {
131        Self(Arc::new(Mutex::new(descriptor.clone())))
132    }
133
134    /// Returns the current default [`ImageSamplerDescriptor`].
135    pub fn get(&self) -> ImageSamplerDescriptor {
136        self.0.lock().unwrap().clone()
137    }
138
139    /// Makes a clone of internal [`Arc`] pointer.
140    ///
141    /// Intended only to be used by code with no access to ECS.
142    pub fn get_internal(&self) -> Arc<Mutex<ImageSamplerDescriptor>> {
143        self.0.clone()
144    }
145
146    /// Replaces default [`ImageSamplerDescriptor`].
147    ///
148    /// Doesn't apply to samplers already built on top of it, i.e. `GltfLoader`'s output.
149    /// Assets need to manually be reloaded.
150    pub fn set(&self, descriptor: &ImageSamplerDescriptor) {
151        *self.0.lock().unwrap() = descriptor.clone();
152    }
153}
154
155/// Adds support for glTF file loading to the app.
156pub struct GltfPlugin {
157    /// The default image sampler to lay glTF sampler data on top of.
158    ///
159    /// Can be modified with the [`DefaultGltfImageSampler`] resource.
160    pub default_sampler: ImageSamplerDescriptor,
161
162    /// _CAUTION: This is an experimental feature with [known issues](https://github.com/bevyengine/bevy/issues/20621). Behavior may change in future versions._
163    ///
164    /// How to convert glTF coordinates on import. Assuming glTF cameras, glTF lights, and glTF meshes had global identity transforms,
165    /// their Bevy [`Transform::forward`](bevy_transform::components::Transform::forward) will be pointing in the following global directions:
166    /// - When set to `false`
167    ///   - glTF cameras and glTF lights: global -Z,
168    ///   - glTF models: global +Z.
169    /// - When set to `true`
170    ///   - glTF cameras and glTF lights: global +Z,
171    ///   - glTF models: global -Z.
172    ///
173    /// The default is `false`.
174    pub use_model_forward_direction: bool,
175
176    /// Registry for custom vertex attributes.
177    ///
178    /// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
179    pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
180}
181
182impl Default for GltfPlugin {
183    fn default() -> Self {
184        GltfPlugin {
185            default_sampler: ImageSamplerDescriptor::linear(),
186            custom_vertex_attributes: HashMap::default(),
187            use_model_forward_direction: false,
188        }
189    }
190}
191
192impl GltfPlugin {
193    /// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].
194    ///
195    /// `name` must be the attribute name as found in the glTF data, which must start with an underscore.
196    /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
197    /// for additional details on custom attributes.
198    pub fn add_custom_vertex_attribute(
199        mut self,
200        name: &str,
201        attribute: MeshVertexAttribute,
202    ) -> Self {
203        self.custom_vertex_attributes.insert(name.into(), attribute);
204        self
205    }
206}
207
208impl Plugin for GltfPlugin {
209    fn build(&self, app: &mut App) {
210        app.init_asset::<Gltf>()
211            .init_asset::<GltfNode>()
212            .init_asset::<GltfPrimitive>()
213            .init_asset::<GltfMesh>()
214            .init_asset::<GltfSkin>()
215            .preregister_asset_loader::<GltfLoader>(&["gltf", "glb"]);
216    }
217
218    fn finish(&self, app: &mut App) {
219        let supported_compressed_formats = if let Some(resource) =
220            app.world().get_resource::<CompressedImageFormatSupport>()
221        {
222            resource.0
223        } else {
224            warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \
225            RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");
226            CompressedImageFormats::NONE
227        };
228
229        let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
230        let default_sampler = default_sampler_resource.get_internal();
231        app.insert_resource(default_sampler_resource);
232
233        app.register_asset_loader(GltfLoader {
234            supported_compressed_formats,
235            custom_vertex_attributes: self.custom_vertex_attributes.clone(),
236            default_sampler,
237            default_use_model_forward_direction: self.use_model_forward_direction,
238        });
239    }
240}