#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] //! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions //! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy. //! //! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files. //! //! # Quick Start //! //! Here's how to spawn a simple glTF scene //! //! ``` //! # use bevy_ecs::prelude::*; //! # use bevy_asset::prelude::*; //! # use bevy_scene::prelude::*; //! # use bevy_transform::prelude::*; //! # use bevy_gltf::prelude::*; //! //! fn spawn_gltf(mut commands: Commands, asset_server: Res) { //! commands.spawn(SceneBundle { //! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0" //! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file. //! // If this isn't specified bevy doesn't know which part of the glTF file to load. //! scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), //! // You can use the transform to give it a position //! transform: Transform::from_xyz(2.0, 0.0, -5.0), //! ..Default::default() //! }); //! } //! ``` //! # Loading parts of a glTF asset //! //! ## Using `Gltf` //! //! If you want to access part of the asset, you can load the entire `Gltf` using the `AssetServer`. //! Once the `Handle` is loaded you can then use it to access named parts of it. //! //! ``` //! # use bevy_ecs::prelude::*; //! # use bevy_asset::prelude::*; //! # use bevy_scene::prelude::*; //! # use bevy_transform::prelude::*; //! # use bevy_gltf::Gltf; //! //! // Holds the scene handle //! #[derive(Resource)] //! struct HelmetScene(Handle); //! //! fn load_gltf(mut commands: Commands, asset_server: Res) { //! let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf"); //! commands.insert_resource(HelmetScene(gltf)); //! } //! //! fn spawn_gltf_objects( //! mut commands: Commands, //! helmet_scene: Res, //! gltf_assets: Res>, //! mut loaded: Local, //! ) { //! // Only do this once //! if *loaded { //! return; //! } //! // Wait until the scene is loaded //! let Some(gltf) = gltf_assets.get(&helmet_scene.0) else { //! return; //! }; //! *loaded = true; //! //! commands.spawn(SceneBundle { //! // Gets the first scene in the file //! scene: gltf.scenes[0].clone(), //! ..Default::default() //! }); //! //! commands.spawn(SceneBundle { //! // Gets the scene named "Lenses_low" //! scene: gltf.named_scenes["Lenses_low"].clone(), //! transform: Transform::from_xyz(1.0, 2.0, 3.0), //! ..Default::default() //! }); //! } //! ``` //! //! ## Asset Labels //! //! The glTF loader let's you specify labels that let you target specific parts of the glTF. //! //! Be careful when using this feature, if you misspell a label it will simply ignore it without warning. //! //! You can use [`GltfAssetLabel`] to ensure you are using the correct label. #[cfg(feature = "bevy_animation")] use bevy_animation::AnimationClip; use bevy_utils::HashMap; mod loader; mod vertex_attributes; pub use loader::*; use bevy_app::prelude::*; use bevy_asset::{Asset, AssetApp, AssetPath, Handle}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_pbr::StandardMaterial; use bevy_reflect::{Reflect, TypePath}; use bevy_render::{ mesh::{skinning::SkinnedMeshInverseBindposes, Mesh, MeshVertexAttribute}, renderer::RenderDevice, texture::CompressedImageFormats, }; use bevy_scene::Scene; /// The glTF prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { #[doc(hidden)] pub use crate::{Gltf, GltfAssetLabel, GltfExtras}; } /// Adds support for glTF file loading to the app. #[derive(Default)] pub struct GltfPlugin { custom_vertex_attributes: HashMap, MeshVertexAttribute>, } impl GltfPlugin { /// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`]. /// /// `name` must be the attribute name as found in the glTF data, which must start with an underscore. /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) /// for additional details on custom attributes. pub fn add_custom_vertex_attribute( mut self, name: &str, attribute: MeshVertexAttribute, ) -> Self { self.custom_vertex_attributes.insert(name.into(), attribute); self } } impl Plugin for GltfPlugin { fn build(&self, app: &mut App) { app.register_type::() .register_type::() .register_type::() .register_type::() .init_asset::() .init_asset::() .init_asset::() .init_asset::() .init_asset::() .preregister_asset_loader::(&["gltf", "glb"]); } fn finish(&self, app: &mut App) { let supported_compressed_formats = match app.world().get_resource::() { Some(render_device) => CompressedImageFormats::from_features(render_device.features()), None => CompressedImageFormats::NONE, }; app.register_asset_loader(GltfLoader { supported_compressed_formats, custom_vertex_attributes: self.custom_vertex_attributes.clone(), }); } } /// Representation of a loaded glTF file. #[derive(Asset, Debug, TypePath)] pub struct Gltf { /// All scenes loaded from the glTF file. pub scenes: Vec>, /// Named scenes loaded from the glTF file. pub named_scenes: HashMap, Handle>, /// All meshes loaded from the glTF file. pub meshes: Vec>, /// Named meshes loaded from the glTF file. pub named_meshes: HashMap, Handle>, /// All materials loaded from the glTF file. pub materials: Vec>, /// Named materials loaded from the glTF file. pub named_materials: HashMap, Handle>, /// All nodes loaded from the glTF file. pub nodes: Vec>, /// Named nodes loaded from the glTF file. pub named_nodes: HashMap, Handle>, /// All skins loaded from the glTF file. pub skins: Vec>, /// Named skins loaded from the glTF file. pub named_skins: HashMap, Handle>, /// Default scene to be displayed. pub default_scene: Option>, /// All animations loaded from the glTF file. #[cfg(feature = "bevy_animation")] pub animations: Vec>, /// Named animations loaded from the glTF file. #[cfg(feature = "bevy_animation")] pub named_animations: HashMap, Handle>, /// The gltf root of the gltf asset, see . Only has a value when `GltfLoaderSettings::include_source` is true. pub source: Option, } /// A glTF node with all of its child nodes, its [`GltfMesh`], /// [`Transform`](bevy_transform::prelude::Transform), its optional [`GltfSkin`] /// and an optional [`GltfExtras`]. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node). #[derive(Asset, Debug, Clone, TypePath)] pub struct GltfNode { /// Index of the node inside the scene pub index: usize, /// Computed name for a node - either a user defined node name from gLTF or a generated name from index pub name: String, /// Direct children of the node. pub children: Vec>, /// Mesh of the node. pub mesh: Option>, /// Skin of the node. pub skin: Option>, /// Local transform. pub transform: bevy_transform::prelude::Transform, /// Is this node used as an animation root #[cfg(feature = "bevy_animation")] pub is_animation_root: bool, /// Additional data. pub extras: Option, } impl GltfNode { /// Create a node extracting name and index from glTF def pub fn new( node: &gltf::Node, children: Vec>, mesh: Option>, transform: bevy_transform::prelude::Transform, skin: Option>, extras: Option, ) -> Self { Self { index: node.index(), name: if let Some(name) = node.name() { name.to_string() } else { format!("GltfNode{}", node.index()) }, children, mesh, transform, skin, #[cfg(feature = "bevy_animation")] is_animation_root: false, extras, } } /// Create a node with animation root mark #[cfg(feature = "bevy_animation")] pub fn with_animation_root(self, is_animation_root: bool) -> Self { Self { is_animation_root, ..self } } /// Subasset label for this node within the gLTF parent asset. pub fn asset_label(&self) -> GltfAssetLabel { GltfAssetLabel::Node(self.index) } } /// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes) /// and an optional [`GltfExtras`]. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin). #[derive(Asset, Debug, Clone, TypePath)] pub struct GltfSkin { /// Index of the skin inside the scene pub index: usize, /// Computed name for a skin - either a user defined skin name from gLTF or a generated name from index pub name: String, /// All the nodes that form this skin. pub joints: Vec>, /// Inverse-bind matricy of this skin. pub inverse_bind_matrices: Handle, /// Additional data. pub extras: Option, } impl GltfSkin { /// Create a skin extracting name and index from glTF def pub fn new( skin: &gltf::Skin, joints: Vec>, inverse_bind_matrices: Handle, extras: Option, ) -> Self { Self { index: skin.index(), name: if let Some(name) = skin.name() { name.to_string() } else { format!("GltfSkin{}", skin.index()) }, joints, inverse_bind_matrices, extras, } } /// Subasset label for this skin within the gLTF parent asset. pub fn asset_label(&self) -> GltfAssetLabel { GltfAssetLabel::Skin(self.index) } } /// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive) /// and an optional [`GltfExtras`]. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh). #[derive(Asset, Debug, Clone, TypePath)] pub struct GltfMesh { /// Index of the mesh inside the scene pub index: usize, /// Computed name for a mesh - either a user defined mesh name from gLTF or a generated name from index pub name: String, /// Primitives of the glTF mesh. pub primitives: Vec, /// Additional data. pub extras: Option, } impl GltfMesh { /// Create a mesh extracting name and index from glTF def pub fn new( mesh: &gltf::Mesh, primitives: Vec, extras: Option, ) -> Self { Self { index: mesh.index(), name: if let Some(name) = mesh.name() { name.to_string() } else { format!("GltfMesh{}", mesh.index()) }, primitives, extras, } } /// Subasset label for this mesh within the gLTF parent asset. pub fn asset_label(&self) -> GltfAssetLabel { GltfAssetLabel::Mesh(self.index) } } /// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`]. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive). #[derive(Asset, Debug, Clone, TypePath)] pub struct GltfPrimitive { /// Index of the primitive inside the mesh pub index: usize, /// Index of the parent [`GltfMesh`] of this primitive pub parent_mesh_index: usize, /// Computed name for a primitive - either a user defined primitive name from gLTF or a generated name from index pub name: String, /// Topology to be rendered. pub mesh: Handle, /// Material to apply to the `mesh`. pub material: Option>, /// Additional data. pub extras: Option, /// Additional data of the `material`. pub material_extras: Option, } impl GltfPrimitive { /// Create a primitive extracting name and index from glTF def pub fn new( gltf_mesh: &gltf::Mesh, gltf_primitive: &gltf::Primitive, mesh: Handle, material: Option>, extras: Option, material_extras: Option, ) -> Self { GltfPrimitive { index: gltf_primitive.index(), parent_mesh_index: gltf_mesh.index(), name: { let mesh_name = gltf_mesh.name().unwrap_or("Mesh"); if gltf_mesh.primitives().len() > 1 { format!("{}.{}", mesh_name, gltf_primitive.index()) } else { mesh_name.to_string() } }, mesh, material, extras, material_extras, } } /// Subasset label for this primitive within its parent [`GltfMesh`] within the gLTF parent asset. pub fn asset_label(&self) -> GltfAssetLabel { GltfAssetLabel::Primitive { mesh: self.parent_mesh_index, primitive: self.index, } } } /// Additional untyped data that can be present on most glTF types at the primitive level. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] #[reflect(Component)] pub struct GltfExtras { /// Content of the extra data. pub value: String, } /// Additional untyped data that can be present on most glTF types at the scene level. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] #[reflect(Component)] pub struct GltfSceneExtras { /// Content of the extra data. pub value: String, } /// Additional untyped data that can be present on most glTF types at the mesh level. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] #[reflect(Component)] pub struct GltfMeshExtras { /// Content of the extra data. pub value: String, } /// Additional untyped data that can be present on most glTF types at the material level. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] #[reflect(Component)] pub struct GltfMaterialExtras { /// Content of the extra data. pub value: String, } /// Labels that can be used to load part of a glTF /// /// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path /// /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_asset::prelude::*; /// # use bevy_scene::prelude::*; /// # use bevy_gltf::prelude::*; /// /// fn load_gltf_scene(asset_server: Res) { /// let gltf_scene: Handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")); /// } /// ``` /// /// Or when formatting a string for the path /// /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_asset::prelude::*; /// # use bevy_scene::prelude::*; /// # use bevy_gltf::prelude::*; /// /// fn load_gltf_scene(asset_server: Res) { /// let gltf_scene: Handle = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0))); /// } /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GltfAssetLabel { /// `Scene{}`: glTF Scene as a Bevy `Scene` Scene(usize), /// `Node{}`: glTF Node as a `GltfNode` Node(usize), /// `Mesh{}`: glTF Mesh as a `GltfMesh` Mesh(usize), /// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy `Mesh` Primitive { /// Index of the mesh for this primitive mesh: usize, /// Index of this primitive in its parent mesh primitive: usize, }, /// `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive MorphTarget { /// Index of the mesh for this primitive mesh: usize, /// Index of this primitive in its parent mesh primitive: usize, }, /// `Texture{}`: glTF Texture as a Bevy `Image` Texture(usize), /// `Material{}`: glTF Material as a Bevy `StandardMaterial` Material { /// Index of this material index: usize, /// Used to set the [`Face`](bevy_render::render_resource::Face) of the material, useful if it is used with negative scale is_scale_inverted: bool, }, /// `DefaultMaterial`: as above, if the glTF file contains a default material with no index DefaultMaterial, /// `Animation{}`: glTF Animation as Bevy `AnimationClip` Animation(usize), /// `Skin{}`: glTF mesh skin as `GltfSkin` Skin(usize), /// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy `SkinnedMeshInverseBindposes` InverseBindMatrices(usize), } impl std::fmt::Display for GltfAssetLabel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GltfAssetLabel::Scene(index) => f.write_str(&format!("Scene{index}")), GltfAssetLabel::Node(index) => f.write_str(&format!("Node{index}")), GltfAssetLabel::Mesh(index) => f.write_str(&format!("Mesh{index}")), GltfAssetLabel::Primitive { mesh, primitive } => { f.write_str(&format!("Mesh{mesh}/Primitive{primitive}")) } GltfAssetLabel::MorphTarget { mesh, primitive } => { f.write_str(&format!("Mesh{mesh}/Primitive{primitive}/MorphTargets")) } GltfAssetLabel::Texture(index) => f.write_str(&format!("Texture{index}")), GltfAssetLabel::Material { index, is_scale_inverted, } => f.write_str(&format!( "Material{index}{}", if *is_scale_inverted { " (inverted)" } else { "" } )), GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"), GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")), GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")), GltfAssetLabel::InverseBindMatrices(index) => { f.write_str(&format!("Skin{index}/InverseBindMatrices")) } } } } impl GltfAssetLabel { /// Add this label to an asset path /// /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_asset::prelude::*; /// # use bevy_scene::prelude::*; /// # use bevy_gltf::prelude::*; /// /// fn load_gltf_scene(asset_server: Res) { /// let gltf_scene: Handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")); /// } /// ``` pub fn from_asset(&self, path: impl Into>) -> AssetPath<'static> { path.into().with_label(self.to_string()) } }