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_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("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<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};
#[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<Image> {
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,

View file

@ -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::*;

View file

@ -42,7 +42,13 @@ fn setup(
mut materials: ResMut<Assets<CustomMaterial>>,
) {
// 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 {}),

View file

@ -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()
});

View file

@ -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()
});

View file

@ -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()
})

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) {
// 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()

View file

@ -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(),

View file

@ -88,7 +88,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, 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()
});

View file

@ -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,

View file

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

View file

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

View file

@ -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()
});
}

View file

@ -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()
});

View file

@ -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()
})

View file

@ -93,7 +93,9 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// 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<AssetServer>) {
// 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()

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
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,

View file

@ -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);

View file

@ -29,7 +29,10 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// 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()
});

View file

@ -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()
});

View file

@ -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()
});

View file

@ -27,7 +27,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// 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()
});
}

View file

@ -36,11 +36,19 @@ struct MorphData {
fn setup(asset_server: Res<AssetServer>, 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 {

View file

@ -16,15 +16,27 @@ fn setup(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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<T> 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<T> storage:
let material_handle = materials.add(StandardMaterial {

View file

@ -16,7 +16,8 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// 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.

View file

@ -133,7 +133,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, 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<AssetServer>, 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(

View file

@ -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());

View file

@ -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 = [
(

View file

@ -13,7 +13,7 @@ fn main() {
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// 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