Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 125 additions & 14 deletions crates/bevy_gltf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand All @@ -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};
Expand All @@ -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 {
Expand Down Expand Up @@ -251,3 +248,117 @@ pub struct GltfExtras {
/// Content of the extra data.
pub value: String,
}

/// Labels that can be used to load part of a glTF
mockersf marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)));
/// }
/// ```
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())
}
}
44 changes: 26 additions & 18 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -1415,12 +1418,16 @@ fn load_node(

/// Returns the label for the `mesh`.
fn mesh_label(mesh: &gltf::Mesh) -> String {
mockersf marked this conversation as resolved.
Show resolved Hide resolved
format!("Mesh{}", mesh.index())
GltfAssetLabel::Mesh(mesh.index()).to_string()
}

/// Returns the label for the `mesh` and `primitive`.
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
GltfAssetLabel::Primitive {
mesh: mesh.index(),
primitive: primitive.index(),
}
.to_string()
}

fn primitive_name(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
Expand All @@ -1434,28 +1441,29 @@ 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()
)
GltfAssetLabel::MorphTarget {
mesh: mesh.index(),
primitive: primitive.index(),
}
.to_string()
}

/// 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())
GltfAssetLabel::Texture(texture.index()).to_string()
}

fn texture_handle(load_context: &mut LoadContext, texture: &gltf::Texture) -> Handle<Image> {
Expand Down Expand Up @@ -1501,16 +1509,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.
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_internal/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
8 changes: 7 additions & 1 deletion examples/2d/custom_gltf_vertex_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}),
Expand Down
3 changes: 2 additions & 1 deletion examples/3d/anti_aliasing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
});

Expand Down
3 changes: 2 additions & 1 deletion examples/3d/atmospheric_fog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
});

Expand Down
3 changes: 2 additions & 1 deletion examples/3d/clearcoat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand Down
7 changes: 5 additions & 2 deletions examples/3d/color_grading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,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()
Expand Down
3 changes: 2 additions & 1 deletion examples/3d/deferred_rendering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
5 changes: 4 additions & 1 deletion examples/3d/depth_of_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
});

Expand Down
11 changes: 7 additions & 4 deletions examples/3d/irradiance_volumes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,16 +516,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,
Expand Down
Loading
Loading