diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index f2bd5c014c6f3..1f231f0ae1af3 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -19,12 +19,14 @@ //! # 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("models/FlightHelmet/FlightHelmet.gltf#Scene0"), +//! 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() @@ -91,18 +93,7 @@ //! //! Be careful when using this feature, if you misspell a label it will simply ignore it without warning. //! -//! Here's the list of supported labels (`{}` is the index in the file): -//! -//! - `Scene{}`: glTF Scene as a Bevy `Scene` -//! - `Node{}`: glTF Node as a `GltfNode` -//! - `Mesh{}`: glTF Mesh as a `GltfMesh` -//! - `Mesh{}/Primitive{}`: glTF Primitive as a Bevy `Mesh` -//! - `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive -//! - `Texture{}`: glTF Texture as a Bevy `Image` -//! - `Material{}`: glTF Material as a Bevy `StandardMaterial` -//! - `DefaultMaterial`: as above, if the glTF file contains a default material with no index -//! - `Animation{}`: glTF Animation as Bevy `AnimationClip` -//! - `Skin{}`: glTF mesh skin as Bevy `SkinnedMeshInverseBindposes` +//! You can use [`GltfAssetLabel`] to ensure you are using the correct label. #[cfg(feature = "bevy_animation")] use bevy_animation::AnimationClip; @@ -113,7 +104,7 @@ mod vertex_attributes; pub use loader::*; use bevy_app::prelude::*; -use bevy_asset::{Asset, AssetApp, Handle}; +use bevy_asset::{Asset, AssetApp, AssetPath, Handle}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_pbr::StandardMaterial; use bevy_reflect::{Reflect, TypePath}; @@ -124,6 +115,12 @@ use bevy_render::{ }; 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 { @@ -251,3 +248,118 @@ pub struct GltfExtras { /// 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)] +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) { + /// 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()) + } +} diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index bfdec7aaacdc8..7d1a3a4c8cf6f 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1,3 +1,4 @@ +use crate::GltfAssetLabel; use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode}; #[cfg(feature = "bevy_animation")] use bevy_animation::{AnimationTarget, AnimationTargetId}; @@ -316,8 +317,10 @@ async fn load_gltf<'a, 'b, 'c>( ); } } - let handle = load_context - .add_labeled_asset(format!("Animation{}", animation.index()), animation_clip); + let handle = load_context.add_labeled_asset( + GltfAssetLabel::Animation(animation.index()).to_string(), + animation_clip, + ); if let Some(name) = animation.name() { named_animations.insert(name.into(), handle.clone()); } @@ -337,7 +340,9 @@ async fn load_gltf<'a, 'b, 'c>( texture: ImageOrPath, ) { let handle = match texture { - ImageOrPath::Image { label, image } => load_context.add_labeled_asset(label, image), + ImageOrPath::Image { label, image } => { + load_context.add_labeled_asset(label.to_string(), image) + } ImageOrPath::Path { path, is_srgb, @@ -435,7 +440,10 @@ async fn load_gltf<'a, 'b, 'c>( for gltf_mesh in gltf.meshes() { let mut primitives = vec![]; for primitive in gltf_mesh.primitives() { - let primitive_label = primitive_label(&gltf_mesh, &primitive); + let primitive_label = GltfAssetLabel::Primitive { + mesh: gltf_mesh.index(), + primitive: primitive.index(), + }; let primitive_topology = get_primitive_topology(primitive.mode())?; let mut mesh = Mesh::new(primitive_topology, settings.load_meshes); @@ -478,14 +486,17 @@ async fn load_gltf<'a, 'b, 'c>( { let morph_target_reader = reader.read_morph_targets(); if morph_target_reader.len() != 0 { - let morph_targets_label = morph_targets_label(&gltf_mesh, &primitive); + let morph_targets_label = GltfAssetLabel::MorphTarget { + mesh: gltf_mesh.index(), + primitive: primitive.index(), + }; let morph_target_image = MorphTargetImage::new( morph_target_reader.map(PrimitiveMorphAttributesIter), mesh.count_vertices(), RenderAssetUsages::default(), )?; - let handle = - load_context.add_labeled_asset(morph_targets_label, morph_target_image.0); + let handle = load_context + .add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0); mesh.set_morph_targets(handle); let extras = gltf_mesh.extras().as_ref(); @@ -540,7 +551,7 @@ async fn load_gltf<'a, 'b, 'c>( }); } - let mesh = load_context.add_labeled_asset(primitive_label, mesh); + let mesh = load_context.add_labeled_asset(primitive_label.to_string(), mesh); primitives.push(super::GltfPrimitive { mesh, material: primitive @@ -553,7 +564,7 @@ async fn load_gltf<'a, 'b, 'c>( } let handle = load_context.add_labeled_asset( - mesh_label(&gltf_mesh), + GltfAssetLabel::Mesh(gltf_mesh.index()).to_string(), super::GltfMesh { primitives, extras: get_gltf_extras(gltf_mesh.extras()), @@ -808,7 +819,7 @@ async fn load_image<'a, 'b>( )?; Ok(ImageOrPath::Image { image, - label: texture_label(&gltf_texture), + label: GltfAssetLabel::Texture(gltf_texture.index()), }) } gltf::image::Source::Uri { uri, mime_type } => { @@ -830,7 +841,7 @@ async fn load_image<'a, 'b>( ImageSampler::Descriptor(sampler_descriptor), render_asset_usages, )?, - label: texture_label(&gltf_texture), + label: GltfAssetLabel::Texture(gltf_texture.index()), }) } else { let image_path = parent_path.join(uri); @@ -1247,12 +1258,15 @@ fn load_node( load_material(&material, load_context, document, is_scale_inverted); } - let primitive_label = primitive_label(&mesh, &primitive); + let primitive_label = GltfAssetLabel::Primitive { + mesh: mesh.index(), + primitive: primitive.index(), + }; let bounds = primitive.bounding_box(); let mut mesh_entity = parent.spawn(PbrBundle { // TODO: handle missing label handle errors here? - mesh: load_context.get_label_handle(&primitive_label), + mesh: load_context.get_label_handle(primitive_label.to_string()), material: load_context.get_label_handle(&material_label), ..Default::default() }); @@ -1400,8 +1414,12 @@ fn load_node( // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag if !settings.load_meshes.is_empty() { if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { - let primitive_label = mesh.primitives().next().map(|p| primitive_label(&mesh, &p)); - let first_mesh = primitive_label.map(|label| load_context.get_label_handle(label)); + let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive { + mesh: mesh.index(), + primitive: p.index(), + }); + let first_mesh = + primitive_label.map(|label| load_context.get_label_handle(label.to_string())); node.insert(MorphWeights::new(weights, first_mesh)?); } } @@ -1413,16 +1431,6 @@ fn load_node( } } -/// Returns the label for the `mesh`. -fn mesh_label(mesh: &gltf::Mesh) -> String { - format!("Mesh{}", mesh.index()) -} - -/// Returns the label for the `mesh` and `primitive`. -fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { - format!("Mesh{}/Primitive{}", mesh.index(), primitive.index()) -} - fn primitive_name(mesh: &gltf::Mesh, primitive: &Primitive) -> String { let mesh_name = mesh.name().unwrap_or("Mesh"); if mesh.primitives().len() > 1 { @@ -1432,37 +1440,23 @@ fn primitive_name(mesh: &gltf::Mesh, primitive: &Primitive) -> String { } } -/// Returns the label for the morph target of `primitive`. -fn morph_targets_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { - format!( - "Mesh{}/Primitive{}/MorphTargets", - mesh.index(), - primitive.index() - ) -} - /// Returns the label for the `material`. fn material_label(material: &Material, is_scale_inverted: bool) -> String { if let Some(index) = material.index() { - format!( - "Material{index}{}", - if is_scale_inverted { " (inverted)" } else { "" } - ) + GltfAssetLabel::Material { + index, + is_scale_inverted, + } + .to_string() } else { - "MaterialDefault".to_string() + GltfAssetLabel::DefaultMaterial.to_string() } } -/// Returns the label for the `texture`. -fn texture_label(texture: &gltf::Texture) -> String { - format!("Texture{}", texture.index()) -} - fn texture_handle(load_context: &mut LoadContext, texture: &gltf::Texture) -> Handle { match texture.source().source() { Source::View { .. } => { - let label = texture_label(texture); - load_context.get_label_handle(&label) + load_context.get_label_handle(GltfAssetLabel::Texture(texture.index()).to_string()) } Source::Uri { uri, .. } => { let uri = percent_encoding::percent_decode_str(uri) @@ -1470,8 +1464,7 @@ fn texture_handle(load_context: &mut LoadContext, texture: &gltf::Texture) -> Ha .unwrap(); let uri = uri.as_ref(); if let Ok(_data_uri) = DataUri::parse(uri) { - let label = texture_label(texture); - load_context.get_label_handle(&label) + load_context.get_label_handle(GltfAssetLabel::Texture(texture.index()).to_string()) } else { let parent = load_context.path().parent().unwrap(); let image_path = parent.join(uri); @@ -1501,16 +1494,16 @@ fn texture_handle_from_info( /// Returns the label for the `node`. fn node_label(node: &Node) -> String { - format!("Node{}", node.index()) + GltfAssetLabel::Node(node.index()).to_string() } /// Returns the label for the `scene`. fn scene_label(scene: &gltf::Scene) -> String { - format!("Scene{}", scene.index()) + GltfAssetLabel::Scene(scene.index()).to_string() } fn skin_label(skin: &gltf::Skin) -> String { - format!("Skin{}", skin.index()) + GltfAssetLabel::Skin(skin.index()).to_string() } /// Extracts the texture sampler data from the glTF texture. @@ -1687,7 +1680,7 @@ fn resolve_node_hierarchy( enum ImageOrPath { Image { image: Image, - label: String, + label: GltfAssetLabel, }, Path { path: PathBuf, diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 7566246296d36..70881a3d39a11 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -66,3 +66,7 @@ pub use crate::gilrs::*; #[doc(hidden)] #[cfg(feature = "bevy_state")] pub use crate::state::prelude::*; + +#[doc(hidden)] +#[cfg(feature = "bevy_gltf")] +pub use crate::gltf::prelude::*; diff --git a/examples/2d/custom_gltf_vertex_attribute.rs b/examples/2d/custom_gltf_vertex_attribute.rs index 59c36e754050e..28be7fee61c32 100644 --- a/examples/2d/custom_gltf_vertex_attribute.rs +++ b/examples/2d/custom_gltf_vertex_attribute.rs @@ -42,7 +42,13 @@ fn setup( mut materials: ResMut>, ) { // Add a mesh loaded from a glTF file. This mesh has data for `ATTRIBUTE_BARYCENTRIC`. - let mesh = asset_server.load("models/barycentric/barycentric.gltf#Mesh0/Primitive0"); + let mesh = asset_server.load( + GltfAssetLabel::Primitive { + mesh: 0, + primitive: 0, + } + .from_asset("models/barycentric/barycentric.gltf"), + ); commands.spawn(MaterialMesh2dBundle { mesh: Mesh2dHandle(mesh), material: materials.add(CustomMaterial {}), diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index 6f0af12823d01..81c2156a5554a 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -282,7 +282,8 @@ fn setup( // Flight Helmet commands.spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), ..default() }); diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index 7afef570dd1c3..6bd497a783cc8 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -72,7 +72,8 @@ fn setup_terrain_scene( // Terrain commands.spawn(SceneBundle { - scene: asset_server.load("models/terrain/Mountains.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/terrain/Mountains.gltf")), ..default() }); diff --git a/examples/3d/clearcoat.rs b/examples/3d/clearcoat.rs index 68106a2e00b98..5d977eba7dfc6 100644 --- a/examples/3d/clearcoat.rs +++ b/examples/3d/clearcoat.rs @@ -149,7 +149,8 @@ fn spawn_coated_glass_bubble_sphere( fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) { commands .spawn(SceneBundle { - scene: asset_server.load("models/GolfBall/GolfBall.glb#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")), transform: Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)), ..default() }) diff --git a/examples/3d/color_grading.rs b/examples/3d/color_grading.rs index a5ba8faa150ca..aa4ceea8cd110 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -381,13 +381,16 @@ fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) { // Spawn the main scene. commands.spawn(SceneBundle { - scene: asset_server.load("models/TonemappingTest/TonemappingTest.gltf#Scene0"), + scene: asset_server.load( + GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"), + ), ..default() }); // Spawn the flight helmet. commands.spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), transform: Transform::from_xyz(0.5, 0.0, -0.5) .with_rotation(Quat::from_rotation_y(-0.15 * PI)), ..default() diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index 411d281d65542..7bd5f8937b06f 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -80,7 +80,8 @@ fn setup( }); // FlightHelmet - let helmet_scene = asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"); + let helmet_scene = asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")); commands.spawn(SceneBundle { scene: helmet_scene.clone(), diff --git a/examples/3d/depth_of_field.rs b/examples/3d/depth_of_field.rs index eac63121fb838..61a8bdfd70f2c 100644 --- a/examples/3d/depth_of_field.rs +++ b/examples/3d/depth_of_field.rs @@ -88,7 +88,10 @@ fn setup(mut commands: Commands, asset_server: Res, app_settings: R // Spawn the scene. commands.spawn(SceneBundle { - scene: asset_server.load("models/DepthOfFieldExample/DepthOfFieldExample.glb#Scene0"), + scene: asset_server.load( + GltfAssetLabel::Scene(0) + .from_asset("models/DepthOfFieldExample/DepthOfFieldExample.glb"), + ), ..default() }); diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index 31a9fb8dce802..49d1de8fb0810 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -503,16 +503,19 @@ fn handle_mouse_clicks( impl FromWorld for ExampleAssets { fn from_world(world: &mut World) -> Self { - let fox_animation = world.load_asset("models/animated/Fox.glb#Animation1"); + let fox_animation = + world.load_asset(GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb")); let (fox_animation_graph, fox_animation_node) = AnimationGraph::from_clip(fox_animation.clone()); ExampleAssets { main_sphere: world.add_asset(Sphere::default().mesh().uv(32, 18)), - fox: world.load_asset("models/animated/Fox.glb#Scene0"), + fox: world.load_asset(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), main_sphere_material: world.add_asset(Color::from(SILVER)), - main_scene: world - .load_asset("models/IrradianceVolumeExample/IrradianceVolumeExample.glb#Scene0"), + main_scene: world.load_asset( + GltfAssetLabel::Scene(0) + .from_asset("models/IrradianceVolumeExample/IrradianceVolumeExample.glb"), + ), irradiance_volume: world.load_asset("irradiance_volumes/Example.vxgi.ktx2"), fox_animation_graph: world.add_asset(fox_animation_graph), fox_animation_node, diff --git a/examples/3d/lightmaps.rs b/examples/3d/lightmaps.rs index aeeb753f4d163..564a3162bbca9 100644 --- a/examples/3d/lightmaps.rs +++ b/examples/3d/lightmaps.rs @@ -14,7 +14,8 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(SceneBundle { - scene: asset_server.load("models/CornellBox/CornellBox.glb#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb")), ..default() }); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index c26c8f4e4fc31..358446238e1b1 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -47,7 +47,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }); commands.spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), ..default() }); } diff --git a/examples/3d/reflection_probes.rs b/examples/3d/reflection_probes.rs index c50e6499a0019..3184982cb486f 100644 --- a/examples/3d/reflection_probes.rs +++ b/examples/3d/reflection_probes.rs @@ -98,7 +98,7 @@ fn setup( // Spawns the cubes, light, and camera. fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) { commands.spawn(SceneBundle { - scene: asset_server.load("models/cubes/Cubes.glb#Scene0"), + scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/cubes/Cubes.glb")), ..SceneBundle::default() }); } diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index d10ac00dd28a0..df2b109db1fcd 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -29,7 +29,7 @@ fn setup( }); commands.spawn(SceneBundle { - scene: asset_server.load("models/animated/Fox.glb#Scene0"), + scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), ..default() }); diff --git a/examples/3d/ssr.rs b/examples/3d/ssr.rs index cfac82448519f..e6e53b8462f61 100644 --- a/examples/3d/ssr.rs +++ b/examples/3d/ssr.rs @@ -164,7 +164,8 @@ fn spawn_cube( fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) { commands .spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), transform: Transform::from_scale(Vec3::splat(2.5)), ..default() }) diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index 80205abfdf3b8..406de20dc9802 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -93,7 +93,9 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res) { // Main scene commands .spawn(SceneBundle { - scene: asset_server.load("models/TonemappingTest/TonemappingTest.gltf#Scene0"), + scene: asset_server.load( + GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"), + ), ..default() }) .insert(SceneNumber(1)); @@ -101,7 +103,8 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res) { // Flight Helmet commands.spawn(( SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), transform: Transform::from_xyz(0.5, 0.0, -0.5) .with_rotation(Quat::from_rotation_y(-0.15 * PI)), ..default() diff --git a/examples/3d/update_gltf_scene.rs b/examples/3d/update_gltf_scene.rs index c6ecf541acc1b..344347567233d 100644 --- a/examples/3d/update_gltf_scene.rs +++ b/examples/3d/update_gltf_scene.rs @@ -40,14 +40,16 @@ fn setup(mut commands: Commands, asset_server: Res) { // Spawn the scene as a child of this entity at the given transform commands.spawn(SceneBundle { transform: Transform::from_xyz(-1.0, 0.0, 0.0), - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), ..default() }); // Spawn a second scene, and add a tag component to be able to target it later commands.spawn(( SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), ..default() }, MovedScene, diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index f67cf2a84b898..b70f6047fdc16 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -104,14 +104,18 @@ fn setup( commands .spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")), ..default() }) .insert(MainModel::HighPoly); commands .spawn(SceneBundle { - scene: asset_server.load("models/FlightHelmetLowPoly/FlightHelmetLowPoly.gltf#Scene0"), + scene: asset_server.load( + GltfAssetLabel::Scene(0) + .from_asset("models/FlightHelmetLowPoly/FlightHelmetLowPoly.gltf"), + ), ..default() }) .insert(MainModel::LowPoly); diff --git a/examples/3d/volumetric_fog.rs b/examples/3d/volumetric_fog.rs index 311be092b000b..eeba31a8d3c87 100644 --- a/examples/3d/volumetric_fog.rs +++ b/examples/3d/volumetric_fog.rs @@ -29,7 +29,10 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { // Spawn the glTF scene. commands.spawn(SceneBundle { - scene: asset_server.load("models/VolumetricFogExample/VolumetricFogExample.glb#Scene0"), + scene: asset_server.load( + GltfAssetLabel::Scene(0) + .from_asset("models/VolumetricFogExample/VolumetricFogExample.glb"), + ), ..default() }); diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 5483df3d503e4..da90484f7bb7f 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -41,9 +41,9 @@ fn setup( let animations = graph .add_clips( [ - "models/animated/Fox.glb#Animation2", - "models/animated/Fox.glb#Animation1", - "models/animated/Fox.glb#Animation0", + GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb"), + GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb"), + GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb"), ] .into_iter() .map(|path| asset_server.load(path)), @@ -91,7 +91,7 @@ fn setup( // Fox commands.spawn(SceneBundle { - scene: asset_server.load("models/animated/Fox.glb#Scene0"), + scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), ..default() }); diff --git a/examples/animation/animation_graph.rs b/examples/animation/animation_graph.rs index 497904ae81e6f..bfb9b663712dd 100644 --- a/examples/animation/animation_graph.rs +++ b/examples/animation/animation_graph.rs @@ -160,17 +160,17 @@ fn setup_assets_programmatically( let mut animation_graph = AnimationGraph::new(); let blend_node = animation_graph.add_blend(0.5, animation_graph.root); animation_graph.add_clip( - asset_server.load("models/animated/Fox.glb#Animation0"), + asset_server.load(GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb")), 1.0, animation_graph.root, ); animation_graph.add_clip( - asset_server.load("models/animated/Fox.glb#Animation1"), + asset_server.load(GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb")), 1.0, blend_node, ); animation_graph.add_clip( - asset_server.load("models/animated/Fox.glb#Animation2"), + asset_server.load(GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb")), 1.0, blend_node, ); @@ -236,7 +236,7 @@ fn setup_scene( }); commands.spawn(SceneBundle { - scene: asset_server.load("models/animated/Fox.glb#Scene0"), + scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), transform: Transform::from_scale(Vec3::splat(0.07)), ..default() }); diff --git a/examples/animation/gltf_skinned_mesh.rs b/examples/animation/gltf_skinned_mesh.rs index b7ae0278219ed..f7546b4819d86 100644 --- a/examples/animation/gltf_skinned_mesh.rs +++ b/examples/animation/gltf_skinned_mesh.rs @@ -27,7 +27,8 @@ fn setup(mut commands: Commands, asset_server: Res) { // Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf` commands.spawn(SceneBundle { - scene: asset_server.load("models/SimpleSkin/SimpleSkin.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/SimpleSkin/SimpleSkin.gltf")), ..default() }); } diff --git a/examples/animation/morph_targets.rs b/examples/animation/morph_targets.rs index 3a0d51075438a..2885b0355786e 100644 --- a/examples/animation/morph_targets.rs +++ b/examples/animation/morph_targets.rs @@ -36,11 +36,19 @@ struct MorphData { fn setup(asset_server: Res, mut commands: Commands) { commands.insert_resource(MorphData { - the_wave: asset_server.load("models/animated/MorphStressTest.gltf#Animation2"), - mesh: asset_server.load("models/animated/MorphStressTest.gltf#Mesh0/Primitive0"), + the_wave: asset_server + .load(GltfAssetLabel::Animation(2).from_asset("models/animated/MorphStressTest.gltf")), + mesh: asset_server.load( + GltfAssetLabel::Primitive { + mesh: 0, + primitive: 0, + } + .from_asset("models/animated/MorphStressTest.gltf"), + ), }); commands.spawn(SceneBundle { - scene: asset_server.load("models/animated/MorphStressTest.gltf#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/animated/MorphStressTest.gltf")), ..default() }); commands.spawn(DirectionalLightBundle { diff --git a/examples/asset/asset_loading.rs b/examples/asset/asset_loading.rs index cbb02b934e5fe..d360803616a3e 100644 --- a/examples/asset/asset_loading.rs +++ b/examples/asset/asset_loading.rs @@ -16,15 +16,27 @@ fn setup( mut materials: ResMut>, ) { // By default AssetServer will load assets from inside the "assets" folder. - // For example, the next line will load "ROOT/assets/models/cube/cube.gltf#Mesh0/Primitive0", + // For example, the next line will load GltfAssetLabel::Primitive{mesh:0,primitive:0}.from_asset("ROOT/assets/models/cube/cube.gltf"), // where "ROOT" is the directory of the Application. // // This can be overridden by setting the "CARGO_MANIFEST_DIR" environment variable (see // https://doc.rust-lang.org/cargo/reference/environment-variables.html) // to another directory. When the Application is run through Cargo, "CARGO_MANIFEST_DIR" is // automatically set to your crate (workspace) root directory. - let cube_handle = asset_server.load("models/cube/cube.gltf#Mesh0/Primitive0"); - let sphere_handle = asset_server.load("models/sphere/sphere.gltf#Mesh0/Primitive0"); + let cube_handle = asset_server.load( + GltfAssetLabel::Primitive { + mesh: 0, + primitive: 0, + } + .from_asset("models/cube/cube.gltf"), + ); + let sphere_handle = asset_server.load( + GltfAssetLabel::Primitive { + mesh: 0, + primitive: 0, + } + .from_asset("models/sphere/sphere.gltf"), + ); // All assets end up in their Assets collection once they are done loading: if let Some(sphere) = meshes.get(&sphere_handle) { @@ -49,7 +61,13 @@ fn setup( // It will _not_ be loaded a second time. // The LoadedFolder asset will ultimately also hold handles to the assets, but waiting for it to load // and finding the right handle is more work! - let torus_handle = asset_server.load("models/torus/torus.gltf#Mesh0/Primitive0"); + let torus_handle = asset_server.load( + GltfAssetLabel::Primitive { + mesh: 0, + primitive: 0, + } + .from_asset("models/torus/torus.gltf"), + ); // You can also add assets directly to their Assets storage: let material_handle = materials.add(StandardMaterial { diff --git a/examples/asset/hot_asset_reloading.rs b/examples/asset/hot_asset_reloading.rs index 713654e637b55..317277ac6efb2 100644 --- a/examples/asset/hot_asset_reloading.rs +++ b/examples/asset/hot_asset_reloading.rs @@ -16,7 +16,8 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { // Load our mesh: - let scene_handle = asset_server.load("models/torus/torus.gltf#Scene0"); + let scene_handle = + asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf")); // Any changes to the mesh will be reloaded automatically! Try making a change to torus.gltf. // You should see the changes immediately show up in your app. diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index 700580f12de54..fa1b0543a9661 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -133,7 +133,8 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu }); // spawn the game board - let cell_scene = asset_server.load("models/AlienCake/tile.glb#Scene0"); + let cell_scene = + asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/tile.glb")); game.board = (0..BOARD_SIZE_J) .map(|j| { (0..BOARD_SIZE_I) @@ -163,14 +164,16 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu rotation: Quat::from_rotation_y(-PI / 2.), ..default() }, - scene: asset_server.load("models/AlienCake/alien.glb#Scene0"), + scene: asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/alien.glb")), ..default() }) .id(), ); // load the scene for the cake - game.bonus.handle = asset_server.load("models/AlienCake/cakeBirthday.glb#Scene0"); + game.bonus.handle = + asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/cakeBirthday.glb")); // scoreboard commands.spawn( diff --git a/examples/games/loading_screen.rs b/examples/games/loading_screen.rs index 12a9d4f804a39..d4cab03842e9b 100644 --- a/examples/games/loading_screen.rs +++ b/examples/games/loading_screen.rs @@ -150,7 +150,7 @@ fn load_level_1( )); // Save the asset into the `loading_assets` vector. - let fox = asset_server.load("models/animated/Fox.glb#Scene0"); + let fox = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")); loading_data.loading_assets.push(fox.clone().into()); // Spawn the fox. commands.spawn(( @@ -192,7 +192,8 @@ fn load_level_2( )); // Spawn the helmet. - let helmet_scene = asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"); + let helmet_scene = asset_server + .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")); loading_data .loading_assets .push(helmet_scene.clone().into()); diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 45f3cf2020c5d..b24b7726f95a3 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -118,9 +118,9 @@ fn setup( // Insert a resource with the current scene information let animation_clips = [ - asset_server.load("models/animated/Fox.glb#Animation2"), - asset_server.load("models/animated/Fox.glb#Animation1"), - asset_server.load("models/animated/Fox.glb#Animation0"), + asset_server.load(GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb")), + asset_server.load(GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb")), + asset_server.load(GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb")), ]; let mut animation_graph = AnimationGraph::new(); let node_indices = animation_graph @@ -136,7 +136,8 @@ fn setup( // The foxes in each ring are spaced at least 2m apart around its circumference.' // NOTE: This fox model faces +z - let fox_handle = asset_server.load("models/animated/Fox.glb#Scene0"); + let fox_handle = + asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")); let ring_directions = [ ( diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index 9fbd443b130a3..18d17084d3253 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -13,7 +13,7 @@ fn main() { fn setup_scene(mut commands: Commands, asset_server: Res) { // add entities to the world commands.spawn(SceneBundle { - scene: asset_server.load("models/torus/torus.gltf#Scene0"), + scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf")), ..default() }); // light