glTF labels: add enum to avoid misspelling and keep up-to-date list documented (#13586)

# Objective

- Followup to #13548
- It added a list of all possible labels to documentation. This seems
hard to keep up and doesn't stop people from making spelling mistake

## Solution

- Add an enum that can create all the labels possible, and encourage its
use rather than manually typed labels

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Rob Parrett <robparrett@gmail.com>
This commit is contained in:
François Mockers 2024-06-01 01:25:57 +02:00 committed by GitHub
parent ce46d52536
commit 5559632977
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 293 additions and 117 deletions

View file

@ -19,12 +19,14 @@
//! # use bevy_asset::prelude::*; //! # use bevy_asset::prelude::*;
//! # use bevy_scene::prelude::*; //! # use bevy_scene::prelude::*;
//! # use bevy_transform::prelude::*; //! # use bevy_transform::prelude::*;
//! # use bevy_gltf::prelude::*;
//! //!
//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) { //! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
//! commands.spawn(SceneBundle { //! 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. //! // 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. //! // 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 //! // You can use the transform to give it a position
//! transform: Transform::from_xyz(2.0, 0.0, -5.0), //! transform: Transform::from_xyz(2.0, 0.0, -5.0),
//! ..Default::default() //! ..Default::default()
@ -91,18 +93,7 @@
//! //!
//! Be careful when using this feature, if you misspell a label it will simply ignore it without warning. //! 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): //! You can use [`GltfAssetLabel`] to ensure you are using the correct label.
//!
//! - `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`
#[cfg(feature = "bevy_animation")] #[cfg(feature = "bevy_animation")]
use bevy_animation::AnimationClip; use bevy_animation::AnimationClip;
@ -113,7 +104,7 @@ mod vertex_attributes;
pub use loader::*; pub use loader::*;
use bevy_app::prelude::*; 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_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_pbr::StandardMaterial; use bevy_pbr::StandardMaterial;
use bevy_reflect::{Reflect, TypePath}; use bevy_reflect::{Reflect, TypePath};
@ -124,6 +115,12 @@ use bevy_render::{
}; };
use bevy_scene::Scene; 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. /// Adds support for glTF file loading to the app.
#[derive(Default)] #[derive(Default)]
pub struct GltfPlugin { pub struct GltfPlugin {
@ -251,3 +248,118 @@ pub struct GltfExtras {
/// Content of the extra data. /// Content of the extra data.
pub value: String, 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)]
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())
}
}

View file

@ -1,3 +1,4 @@
use crate::GltfAssetLabel;
use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode}; use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode};
#[cfg(feature = "bevy_animation")] #[cfg(feature = "bevy_animation")]
use bevy_animation::{AnimationTarget, AnimationTargetId}; use bevy_animation::{AnimationTarget, AnimationTargetId};
@ -316,8 +317,10 @@ async fn load_gltf<'a, 'b, 'c>(
); );
} }
} }
let handle = load_context let handle = load_context.add_labeled_asset(
.add_labeled_asset(format!("Animation{}", animation.index()), animation_clip); GltfAssetLabel::Animation(animation.index()).to_string(),
animation_clip,
);
if let Some(name) = animation.name() { if let Some(name) = animation.name() {
named_animations.insert(name.into(), handle.clone()); named_animations.insert(name.into(), handle.clone());
} }
@ -337,7 +340,9 @@ async fn load_gltf<'a, 'b, 'c>(
texture: ImageOrPath, texture: ImageOrPath,
) { ) {
let handle = match texture { 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 { ImageOrPath::Path {
path, path,
is_srgb, is_srgb,
@ -435,7 +440,10 @@ async fn load_gltf<'a, 'b, 'c>(
for gltf_mesh in gltf.meshes() { for gltf_mesh in gltf.meshes() {
let mut primitives = vec![]; let mut primitives = vec![];
for primitive in gltf_mesh.primitives() { 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 primitive_topology = get_primitive_topology(primitive.mode())?;
let mut mesh = Mesh::new(primitive_topology, settings.load_meshes); 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(); let morph_target_reader = reader.read_morph_targets();
if morph_target_reader.len() != 0 { 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( let morph_target_image = MorphTargetImage::new(
morph_target_reader.map(PrimitiveMorphAttributesIter), morph_target_reader.map(PrimitiveMorphAttributesIter),
mesh.count_vertices(), mesh.count_vertices(),
RenderAssetUsages::default(), RenderAssetUsages::default(),
)?; )?;
let handle = let handle = load_context
load_context.add_labeled_asset(morph_targets_label, morph_target_image.0); .add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0);
mesh.set_morph_targets(handle); mesh.set_morph_targets(handle);
let extras = gltf_mesh.extras().as_ref(); 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 { primitives.push(super::GltfPrimitive {
mesh, mesh,
material: primitive material: primitive
@ -553,7 +564,7 @@ async fn load_gltf<'a, 'b, 'c>(
} }
let handle = load_context.add_labeled_asset( let handle = load_context.add_labeled_asset(
mesh_label(&gltf_mesh), GltfAssetLabel::Mesh(gltf_mesh.index()).to_string(),
super::GltfMesh { super::GltfMesh {
primitives, primitives,
extras: get_gltf_extras(gltf_mesh.extras()), extras: get_gltf_extras(gltf_mesh.extras()),
@ -808,7 +819,7 @@ async fn load_image<'a, 'b>(
)?; )?;
Ok(ImageOrPath::Image { Ok(ImageOrPath::Image {
image, image,
label: texture_label(&gltf_texture), label: GltfAssetLabel::Texture(gltf_texture.index()),
}) })
} }
gltf::image::Source::Uri { uri, mime_type } => { gltf::image::Source::Uri { uri, mime_type } => {
@ -830,7 +841,7 @@ async fn load_image<'a, 'b>(
ImageSampler::Descriptor(sampler_descriptor), ImageSampler::Descriptor(sampler_descriptor),
render_asset_usages, render_asset_usages,
)?, )?,
label: texture_label(&gltf_texture), label: GltfAssetLabel::Texture(gltf_texture.index()),
}) })
} else { } else {
let image_path = parent_path.join(uri); let image_path = parent_path.join(uri);
@ -1247,12 +1258,15 @@ fn load_node(
load_material(&material, load_context, document, is_scale_inverted); 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 bounds = primitive.bounding_box();
let mut mesh_entity = parent.spawn(PbrBundle { let mut mesh_entity = parent.spawn(PbrBundle {
// TODO: handle missing label handle errors here? // 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), material: load_context.get_label_handle(&material_label),
..Default::default() ..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 // 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 !settings.load_meshes.is_empty() {
if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) {
let primitive_label = mesh.primitives().next().map(|p| primitive_label(&mesh, &p)); let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive {
let first_mesh = primitive_label.map(|label| load_context.get_label_handle(label)); 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)?); 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 { fn primitive_name(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
let mesh_name = mesh.name().unwrap_or("Mesh"); let mesh_name = mesh.name().unwrap_or("Mesh");
if mesh.primitives().len() > 1 { 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`. /// Returns the label for the `material`.
fn material_label(material: &Material, is_scale_inverted: bool) -> String { fn material_label(material: &Material, is_scale_inverted: bool) -> String {
if let Some(index) = material.index() { if let Some(index) = material.index() {
format!( GltfAssetLabel::Material {
"Material{index}{}", index,
if is_scale_inverted { " (inverted)" } else { "" } is_scale_inverted,
) }
.to_string()
} else { } 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<Image> { fn texture_handle(load_context: &mut LoadContext, texture: &gltf::Texture) -> Handle<Image> {
match texture.source().source() { match texture.source().source() {
Source::View { .. } => { Source::View { .. } => {
let label = texture_label(texture); load_context.get_label_handle(GltfAssetLabel::Texture(texture.index()).to_string())
load_context.get_label_handle(&label)
} }
Source::Uri { uri, .. } => { Source::Uri { uri, .. } => {
let uri = percent_encoding::percent_decode_str(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(); .unwrap();
let uri = uri.as_ref(); let uri = uri.as_ref();
if let Ok(_data_uri) = DataUri::parse(uri) { if let Ok(_data_uri) = DataUri::parse(uri) {
let label = texture_label(texture); load_context.get_label_handle(GltfAssetLabel::Texture(texture.index()).to_string())
load_context.get_label_handle(&label)
} else { } else {
let parent = load_context.path().parent().unwrap(); let parent = load_context.path().parent().unwrap();
let image_path = parent.join(uri); let image_path = parent.join(uri);
@ -1501,16 +1494,16 @@ fn texture_handle_from_info(
/// Returns the label for the `node`. /// Returns the label for the `node`.
fn node_label(node: &Node) -> String { fn node_label(node: &Node) -> String {
format!("Node{}", node.index()) GltfAssetLabel::Node(node.index()).to_string()
} }
/// Returns the label for the `scene`. /// Returns the label for the `scene`.
fn scene_label(scene: &gltf::Scene) -> String { fn scene_label(scene: &gltf::Scene) -> String {
format!("Scene{}", scene.index()) GltfAssetLabel::Scene(scene.index()).to_string()
} }
fn skin_label(skin: &gltf::Skin) -> 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. /// Extracts the texture sampler data from the glTF texture.
@ -1687,7 +1680,7 @@ fn resolve_node_hierarchy(
enum ImageOrPath { enum ImageOrPath {
Image { Image {
image: Image, image: Image,
label: String, label: GltfAssetLabel,
}, },
Path { Path {
path: PathBuf, path: PathBuf,

View file

@ -66,3 +66,7 @@ pub use crate::gilrs::*;
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "bevy_state")] #[cfg(feature = "bevy_state")]
pub use crate::state::prelude::*; pub use crate::state::prelude::*;
#[doc(hidden)]
#[cfg(feature = "bevy_gltf")]
pub use crate::gltf::prelude::*;

View file

@ -42,7 +42,13 @@ fn setup(
mut materials: ResMut<Assets<CustomMaterial>>, mut materials: ResMut<Assets<CustomMaterial>>,
) { ) {
// Add a mesh loaded from a glTF file. This mesh has data for `ATTRIBUTE_BARYCENTRIC`. // 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 { commands.spawn(MaterialMesh2dBundle {
mesh: Mesh2dHandle(mesh), mesh: Mesh2dHandle(mesh),
material: materials.add(CustomMaterial {}), material: materials.add(CustomMaterial {}),

View file

@ -282,7 +282,8 @@ fn setup(
// Flight Helmet // Flight Helmet
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -72,7 +72,8 @@ fn setup_terrain_scene(
// Terrain // Terrain
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -149,7 +149,8 @@ fn spawn_coated_glass_bubble_sphere(
fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) { fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) {
commands commands
.spawn(SceneBundle { .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)), transform: Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
..default() ..default()
}) })

View file

@ -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) { fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) {
// Spawn the main scene. // Spawn the main scene.
commands.spawn(SceneBundle { 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() ..default()
}); });
// Spawn the flight helmet. // Spawn the flight helmet.
commands.spawn(SceneBundle { 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) transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)), .with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default() ..default()

View file

@ -80,7 +80,8 @@ fn setup(
}); });
// FlightHelmet // 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 { commands.spawn(SceneBundle {
scene: helmet_scene.clone(), scene: helmet_scene.clone(),

View file

@ -88,7 +88,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
// Spawn the scene. // Spawn the scene.
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -503,16 +503,19 @@ fn handle_mouse_clicks(
impl FromWorld for ExampleAssets { impl FromWorld for ExampleAssets {
fn from_world(world: &mut World) -> Self { 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) = let (fox_animation_graph, fox_animation_node) =
AnimationGraph::from_clip(fox_animation.clone()); AnimationGraph::from_clip(fox_animation.clone());
ExampleAssets { ExampleAssets {
main_sphere: world.add_asset(Sphere::default().mesh().uv(32, 18)), 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_sphere_material: world.add_asset(Color::from(SILVER)),
main_scene: world main_scene: world.load_asset(
.load_asset("models/IrradianceVolumeExample/IrradianceVolumeExample.glb#Scene0"), GltfAssetLabel::Scene(0)
.from_asset("models/IrradianceVolumeExample/IrradianceVolumeExample.glb"),
),
irradiance_volume: world.load_asset("irradiance_volumes/Example.vxgi.ktx2"), irradiance_volume: world.load_asset("irradiance_volumes/Example.vxgi.ktx2"),
fox_animation_graph: world.add_asset(fox_animation_graph), fox_animation_graph: world.add_asset(fox_animation_graph),
fox_animation_node, fox_animation_node,

View file

@ -14,7 +14,8 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -47,7 +47,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default() ..default()
}); });
commands.spawn(SceneBundle { 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() ..default()
}); });
} }

View file

@ -98,7 +98,7 @@ fn setup(
// Spawns the cubes, light, and camera. // Spawns the cubes, light, and camera.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) { fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneBundle { 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() ..SceneBundle::default()
}); });
} }

View file

@ -29,7 +29,7 @@ fn setup(
}); });
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -164,7 +164,8 @@ fn spawn_cube(
fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) { fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) {
commands commands
.spawn(SceneBundle { .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)), transform: Transform::from_scale(Vec3::splat(2.5)),
..default() ..default()
}) })

View file

@ -93,7 +93,9 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// Main scene // Main scene
commands commands
.spawn(SceneBundle { .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() ..default()
}) })
.insert(SceneNumber(1)); .insert(SceneNumber(1));
@ -101,7 +103,8 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// Flight Helmet // Flight Helmet
commands.spawn(( commands.spawn((
SceneBundle { 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) transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)), .with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default() ..default()

View file

@ -40,14 +40,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn the scene as a child of this entity at the given transform // Spawn the scene as a child of this entity at the given transform
commands.spawn(SceneBundle { commands.spawn(SceneBundle {
transform: Transform::from_xyz(-1.0, 0.0, 0.0), 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() ..default()
}); });
// Spawn a second scene, and add a tag component to be able to target it later // Spawn a second scene, and add a tag component to be able to target it later
commands.spawn(( commands.spawn((
SceneBundle { SceneBundle {
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default() ..default()
}, },
MovedScene, MovedScene,

View file

@ -104,14 +104,18 @@ fn setup(
commands commands
.spawn(SceneBundle { .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() ..default()
}) })
.insert(MainModel::HighPoly); .insert(MainModel::HighPoly);
commands commands
.spawn(SceneBundle { .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() ..default()
}) })
.insert(MainModel::LowPoly); .insert(MainModel::LowPoly);

View file

@ -29,7 +29,10 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn the glTF scene. // Spawn the glTF scene.
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -41,9 +41,9 @@ fn setup(
let animations = graph let animations = graph
.add_clips( .add_clips(
[ [
"models/animated/Fox.glb#Animation2", GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb"),
"models/animated/Fox.glb#Animation1", GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb"),
"models/animated/Fox.glb#Animation0", GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb"),
] ]
.into_iter() .into_iter()
.map(|path| asset_server.load(path)), .map(|path| asset_server.load(path)),
@ -91,7 +91,7 @@ fn setup(
// Fox // Fox
commands.spawn(SceneBundle { 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() ..default()
}); });

View file

@ -160,17 +160,17 @@ fn setup_assets_programmatically(
let mut animation_graph = AnimationGraph::new(); let mut animation_graph = AnimationGraph::new();
let blend_node = animation_graph.add_blend(0.5, animation_graph.root); let blend_node = animation_graph.add_blend(0.5, animation_graph.root);
animation_graph.add_clip( 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, 1.0,
animation_graph.root, animation_graph.root,
); );
animation_graph.add_clip( 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, 1.0,
blend_node, blend_node,
); );
animation_graph.add_clip( 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, 1.0,
blend_node, blend_node,
); );
@ -236,7 +236,7 @@ fn setup_scene(
}); });
commands.spawn(SceneBundle { 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)), transform: Transform::from_scale(Vec3::splat(0.07)),
..default() ..default()
}); });

View file

@ -27,7 +27,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf` // Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf`
commands.spawn(SceneBundle { 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() ..default()
}); });
} }

View file

@ -36,11 +36,19 @@ struct MorphData {
fn setup(asset_server: Res<AssetServer>, mut commands: Commands) { fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.insert_resource(MorphData { commands.insert_resource(MorphData {
the_wave: asset_server.load("models/animated/MorphStressTest.gltf#Animation2"), the_wave: asset_server
mesh: asset_server.load("models/animated/MorphStressTest.gltf#Mesh0/Primitive0"), .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 { 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() ..default()
}); });
commands.spawn(DirectionalLightBundle { commands.spawn(DirectionalLightBundle {

View file

@ -16,15 +16,27 @@ fn setup(
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
) { ) {
// By default AssetServer will load assets from inside the "assets" folder. // 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. // where "ROOT" is the directory of the Application.
// //
// This can be overridden by setting the "CARGO_MANIFEST_DIR" environment variable (see // This can be overridden by setting the "CARGO_MANIFEST_DIR" environment variable (see
// https://doc.rust-lang.org/cargo/reference/environment-variables.html) // https://doc.rust-lang.org/cargo/reference/environment-variables.html)
// to another directory. When the Application is run through Cargo, "CARGO_MANIFEST_DIR" is // to another directory. When the Application is run through Cargo, "CARGO_MANIFEST_DIR" is
// automatically set to your crate (workspace) root directory. // automatically set to your crate (workspace) root directory.
let cube_handle = asset_server.load("models/cube/cube.gltf#Mesh0/Primitive0"); let cube_handle = asset_server.load(
let sphere_handle = asset_server.load("models/sphere/sphere.gltf#Mesh0/Primitive0"); 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<T> collection once they are done loading: // All assets end up in their Assets<T> collection once they are done loading:
if let Some(sphere) = meshes.get(&sphere_handle) { if let Some(sphere) = meshes.get(&sphere_handle) {
@ -49,7 +61,13 @@ fn setup(
// It will _not_ be loaded a second time. // 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 // 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! // 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<T> storage: // You can also add assets directly to their Assets<T> storage:
let material_handle = materials.add(StandardMaterial { let material_handle = materials.add(StandardMaterial {

View file

@ -16,7 +16,8 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Load our mesh: // 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. // 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. // You should see the changes immediately show up in your app.

View file

@ -133,7 +133,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
}); });
// spawn the game board // 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) game.board = (0..BOARD_SIZE_J)
.map(|j| { .map(|j| {
(0..BOARD_SIZE_I) (0..BOARD_SIZE_I)
@ -163,14 +164,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
rotation: Quat::from_rotation_y(-PI / 2.), rotation: Quat::from_rotation_y(-PI / 2.),
..default() ..default()
}, },
scene: asset_server.load("models/AlienCake/alien.glb#Scene0"), scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/alien.glb")),
..default() ..default()
}) })
.id(), .id(),
); );
// load the scene for the cake // 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 // scoreboard
commands.spawn( commands.spawn(

View file

@ -150,7 +150,7 @@ fn load_level_1(
)); ));
// Save the asset into the `loading_assets` vector. // 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()); loading_data.loading_assets.push(fox.clone().into());
// Spawn the fox. // Spawn the fox.
commands.spawn(( commands.spawn((
@ -192,7 +192,8 @@ fn load_level_2(
)); ));
// Spawn the helmet. // 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_data
.loading_assets .loading_assets
.push(helmet_scene.clone().into()); .push(helmet_scene.clone().into());

View file

@ -118,9 +118,9 @@ fn setup(
// Insert a resource with the current scene information // Insert a resource with the current scene information
let animation_clips = [ let animation_clips = [
asset_server.load("models/animated/Fox.glb#Animation2"), asset_server.load(GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb")),
asset_server.load("models/animated/Fox.glb#Animation1"), asset_server.load(GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb")),
asset_server.load("models/animated/Fox.glb#Animation0"), asset_server.load(GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb")),
]; ];
let mut animation_graph = AnimationGraph::new(); let mut animation_graph = AnimationGraph::new();
let node_indices = animation_graph 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.' // The foxes in each ring are spaced at least 2m apart around its circumference.'
// NOTE: This fox model faces +z // 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 = [ let ring_directions = [
( (

View file

@ -13,7 +13,7 @@ fn main() {
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// add entities to the world // add entities to the world
commands.spawn(SceneBundle { 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() ..default()
}); });
// light // light