Part of #13681 # Objective gLTF Assets shouldn't be duplicated between Assets resource and node children. Also changed `asset_label` to be a method as [per previous PR comment](https://github.com/bevyengine/bevy/pull/13558). ## Solution - Made GltfNode children be Handles instead of asset copies. ## Testing - Added tests that actually test loading and hierarchy as previous ones unit tested only one function and that makes little sense. - Made circular nodes an actual loading failure instead of a warning no-op. You [_MUST NOT_ have cycles in gLTF](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#nodes-and-hierarchy) according to the spec. - IMO this is a bugfix, not a breaking change. But in an extremely unlikely event in which you relied on invalid behavior for loading gLTF with cyclic children, you will not be able to do that anymore. You should fix your gLTF file as it's not valid according to gLTF spec. For it to for work someone, it had to be bevy with bevy_animation flag off. --- ## Changelog ### Changed - `GltfNode.children` are now `Vec<Handle<GltfNode>>` instead of `Vec<GltfNode>` - Having children cycles between gLTF nodes in a gLTF document is now an explicit asset loading failure. ## Migration Guide If accessing children, use `Assets<GltfNode>` resource to get the actual child object. #### Before ```rs fn gltf_print_first_node_children_system(gltf_component_query: Query<Handle<Gltf>>, gltf_assets: Res<Assets<Gltf>>, gltf_nodes: Res<Assets<GltfNode>>) { for gltf_handle in gltf_component_query.iter() { let gltf_root = gltf_assets.get(gltf_handle).unwrap(); let first_node_handle = gltf_root.nodes.get(0).unwrap(); let first_node = gltf_nodes.get(first_node_handle).unwrap(); let first_child = first_node.children.get(0).unwrap(); println!("First nodes child node name is {:?)", first_child.name); } } ``` #### After ```rs fn gltf_print_first_node_children_system(gltf_component_query: Query<Handle<Gltf>>, gltf_assets: Res<Assets<Gltf>>, gltf_nodes: Res<Assets<GltfNode>>) { for gltf_handle in gltf_component_query.iter() { let gltf_root = gltf_assets.get(gltf_handle).unwrap(); let first_node_handle = gltf_root.nodes.get(0).unwrap(); let first_node = gltf_nodes.get(first_node_handle).unwrap(); let first_child_handle = first_node.children.get(0).unwrap(); let first_child = gltf_nodes.get(first_child_handle).unwrap(); println!("First nodes child node name is {:?)", first_child.name); } } ```
504 lines
18 KiB
Rust
504 lines
18 KiB
Rust
#![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<AssetServer>) {
|
|
//! 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<Gltf>` 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<Gltf>);
|
|
//!
|
|
//! fn load_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
//! let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf");
|
|
//! commands.insert_resource(HelmetScene(gltf));
|
|
//! }
|
|
//!
|
|
//! fn spawn_gltf_objects(
|
|
//! mut commands: Commands,
|
|
//! helmet_scene: Res<HelmetScene>,
|
|
//! gltf_assets: Res<Assets<Gltf>>,
|
|
//! mut loaded: Local<bool>,
|
|
//! ) {
|
|
//! // 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::{Mesh, MeshVertexAttribute},
|
|
renderer::RenderDevice,
|
|
texture::CompressedImageFormats,
|
|
};
|
|
use bevy_scene::Scene;
|
|
|
|
/// The `bevy_gltf` prelude.
|
|
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<Box<str>, 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::<GltfExtras>()
|
|
.register_type::<GltfSceneExtras>()
|
|
.register_type::<GltfMeshExtras>()
|
|
.register_type::<GltfMaterialExtras>()
|
|
.init_asset::<Gltf>()
|
|
.init_asset::<GltfNode>()
|
|
.init_asset::<GltfPrimitive>()
|
|
.init_asset::<GltfMesh>()
|
|
.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"]);
|
|
}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
let supported_compressed_formats = match app.world().get_resource::<RenderDevice>() {
|
|
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<Handle<Scene>>,
|
|
/// Named scenes loaded from the glTF file.
|
|
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
|
|
/// All meshes loaded from the glTF file.
|
|
pub meshes: Vec<Handle<GltfMesh>>,
|
|
/// Named meshes loaded from the glTF file.
|
|
pub named_meshes: HashMap<Box<str>, Handle<GltfMesh>>,
|
|
/// All materials loaded from the glTF file.
|
|
pub materials: Vec<Handle<StandardMaterial>>,
|
|
/// Named materials loaded from the glTF file.
|
|
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
|
|
/// All nodes loaded from the glTF file.
|
|
pub nodes: Vec<Handle<GltfNode>>,
|
|
/// Named nodes loaded from the glTF file.
|
|
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
|
|
/// Default scene to be displayed.
|
|
pub default_scene: Option<Handle<Scene>>,
|
|
/// All animations loaded from the glTF file.
|
|
#[cfg(feature = "bevy_animation")]
|
|
pub animations: Vec<Handle<AnimationClip>>,
|
|
/// Named animations loaded from the glTF file.
|
|
#[cfg(feature = "bevy_animation")]
|
|
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
|
|
/// The gltf root of the gltf asset, see <https://docs.rs/gltf/latest/gltf/struct.Gltf.html>. Only has a value when `GltfLoaderSettings::include_source` is true.
|
|
pub source: Option<gltf::Gltf>,
|
|
}
|
|
|
|
/// A glTF node with all of its child nodes, its [`GltfMesh`],
|
|
/// [`Transform`](bevy_transform::prelude::Transform) 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<Handle<GltfNode>>,
|
|
/// Mesh of the node.
|
|
pub mesh: Option<Handle<GltfMesh>>,
|
|
/// Local transform.
|
|
pub transform: bevy_transform::prelude::Transform,
|
|
/// Additional data.
|
|
pub extras: Option<GltfExtras>,
|
|
}
|
|
|
|
impl GltfNode {
|
|
/// Create a node extracting name and index from glTF def
|
|
pub fn new(
|
|
node: &gltf::Node,
|
|
children: Vec<Handle<GltfNode>>,
|
|
mesh: Option<Handle<GltfMesh>>,
|
|
transform: bevy_transform::prelude::Transform,
|
|
extras: Option<GltfExtras>,
|
|
) -> Self {
|
|
Self {
|
|
index: node.index(),
|
|
name: if let Some(name) = node.name() {
|
|
name.to_string()
|
|
} else {
|
|
format!("GltfNode{}", node.index())
|
|
},
|
|
children,
|
|
mesh,
|
|
transform,
|
|
extras,
|
|
}
|
|
}
|
|
|
|
/// Subasset label for this node within the gLTF parent asset.
|
|
pub fn asset_label(&self) -> GltfAssetLabel {
|
|
GltfAssetLabel::Node(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<GltfPrimitive>,
|
|
/// Additional data.
|
|
pub extras: Option<GltfExtras>,
|
|
}
|
|
|
|
impl GltfMesh {
|
|
/// Create a mesh extracting name and index from glTF def
|
|
pub fn new(
|
|
mesh: &gltf::Mesh,
|
|
primitives: Vec<GltfPrimitive>,
|
|
extras: Option<GltfExtras>,
|
|
) -> 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<Mesh>,
|
|
/// Material to apply to the `mesh`.
|
|
pub material: Option<Handle<StandardMaterial>>,
|
|
/// Additional data.
|
|
pub extras: Option<GltfExtras>,
|
|
/// Additional data of the `material`.
|
|
pub material_extras: Option<GltfExtras>,
|
|
}
|
|
|
|
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<Mesh>,
|
|
material: Option<Handle<StandardMaterial>>,
|
|
extras: Option<GltfExtras>,
|
|
material_extras: Option<GltfExtras>,
|
|
) -> 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<AssetServer>) {
|
|
/// let gltf_scene: Handle<Scene> = 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<AssetServer>) {
|
|
/// let gltf_scene: Handle<Scene> = 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 Bevy `SkinnedMeshInverseBindposes`
|
|
Skin(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}")),
|
|
}
|
|
}
|
|
}
|
|
|
|
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<AssetServer>) {
|
|
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
|
|
/// }
|
|
/// ```
|
|
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
|
|
path.into().with_label(self.to_string())
|
|
}
|
|
}
|