Skip to content

Commit 78a3aae

Browse files
feat(gltf): add name component to gltf mesh primitive (#13912)
# Objective - fixes #13473 ## Solution - When a single mesh is assigned multiple materials, it is divided into several primitive nodes, with each primitive assigned a unique material. Presently, these primitives are named using the format Mesh.index, which complicates querying. To improve this, we can assign a specific name to each primitive based on the material’s name, since each primitive corresponds to one material exclusively. ## Testing - I have included a simple example which shows how to query a material and mesh part based on the new name component. ## Changelog - adds `GltfMaterialName` component to the mesh entity of the gltf primitive node. --------- Co-authored-by: Alice Cecile <[email protected]>
1 parent 5289e18 commit 78a3aae

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,17 @@ description = "Loads and renders a glTF file as a scene, including the gltf extr
910910
category = "3D Rendering"
911911
wasm = true
912912

913+
[[example]]
914+
name = "query_gltf_primitives"
915+
path = "examples/3d/query_gltf_primitives.rs"
916+
doc-scrape-examples = true
917+
918+
[package.metadata.example.query_gltf_primitives]
919+
name = "Query glTF primitives"
920+
description = "Query primitives in a glTF scene"
921+
category = "3D Rendering"
922+
wasm = true
923+
913924
[[example]]
914925
name = "motion_blur"
915926
path = "examples/3d/motion_blur.rs"
4.32 KB
Binary file not shown.

crates/bevy_gltf/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ impl Plugin for GltfPlugin {
153153
.register_type::<GltfSceneExtras>()
154154
.register_type::<GltfMeshExtras>()
155155
.register_type::<GltfMaterialExtras>()
156+
.register_type::<GltfMaterialName>()
156157
.init_asset::<Gltf>()
157158
.init_asset::<GltfNode>()
158159
.init_asset::<GltfPrimitive>()
@@ -460,6 +461,13 @@ pub struct GltfMaterialExtras {
460461
pub value: String,
461462
}
462463

464+
/// The material name of a glTF primitive.
465+
///
466+
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material).
467+
#[derive(Clone, Debug, Reflect, Default, Component)]
468+
#[reflect(Component)]
469+
pub struct GltfMaterialName(pub String);
470+
463471
/// Labels that can be used to load part of a glTF
464472
///
465473
/// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path

crates/bevy_gltf/src/loader.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras,
3-
GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin,
3+
GltfMaterialName, GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin,
44
};
55

66
use alloc::collections::VecDeque;
@@ -1380,6 +1380,10 @@ fn load_node(
13801380
});
13811381
}
13821382

1383+
if let Some(name) = material.name() {
1384+
mesh_entity.insert(GltfMaterialName(String::from(name)));
1385+
}
1386+
13831387
mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive)));
13841388
// Mark for adding skinned mesh
13851389
if let Some(skin) = gltf_node.skin() {

examples/3d/query_gltf_primitives.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//! This example demonstrates how to query a [`StandardMaterial`] within a glTF scene.
2+
//! It is particularly useful for glTF scenes with a mesh that consists of multiple primitives.
3+
4+
use std::f32::consts::PI;
5+
6+
use bevy::{
7+
gltf::GltfMaterialName,
8+
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
9+
prelude::*,
10+
render::mesh::VertexAttributeValues,
11+
};
12+
13+
fn main() {
14+
App::new()
15+
.insert_resource(DirectionalLightShadowMap { size: 4096 })
16+
.add_plugins(DefaultPlugins)
17+
.add_systems(Startup, setup)
18+
.add_systems(Update, find_top_material_and_mesh)
19+
.run();
20+
}
21+
22+
fn find_top_material_and_mesh(
23+
mut materials: ResMut<Assets<StandardMaterial>>,
24+
mut meshes: ResMut<Assets<Mesh>>,
25+
time: Res<Time>,
26+
mat_query: Query<(&Handle<StandardMaterial>, &Handle<Mesh>, &GltfMaterialName)>,
27+
) {
28+
for (mat_handle, mesh_handle, name) in mat_query.iter() {
29+
// locate a material by material name
30+
if name.0 == "Top" {
31+
if let Some(material) = materials.get_mut(mat_handle) {
32+
if let Color::Hsla(ref mut hsla) = material.base_color {
33+
*hsla = hsla.rotate_hue(time.delta_seconds() * 100.0);
34+
} else {
35+
material.base_color = Color::from(Hsla::hsl(0.0, 0.8, 0.5));
36+
}
37+
}
38+
39+
if let Some(mesh) = meshes.get_mut(mesh_handle) {
40+
if let Some(VertexAttributeValues::Float32x3(positions)) =
41+
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
42+
{
43+
positions[0] = (
44+
ops::sin(2.0 * PI * time.elapsed_seconds()),
45+
positions[0][1],
46+
positions[0][2],
47+
)
48+
.into();
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
56+
commands.spawn((
57+
Camera3dBundle {
58+
transform: Transform::from_xyz(0.6, 1.6, 11.3)
59+
.looking_at(Vec3::new(0.0, 0.0, 3.0), Vec3::Y),
60+
..default()
61+
},
62+
EnvironmentMapLight {
63+
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
64+
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
65+
intensity: 500.0,
66+
rotation: Quat::IDENTITY,
67+
},
68+
));
69+
70+
commands.spawn(DirectionalLightBundle {
71+
directional_light: DirectionalLight {
72+
shadows_enabled: true,
73+
..default()
74+
},
75+
// This is a relatively small scene, so use tighter shadow
76+
// cascade bounds than the default for better quality.
77+
// We also adjusted the shadow map to be larger since we're
78+
// only using a single cascade.
79+
cascade_shadow_config: CascadeShadowConfigBuilder {
80+
num_cascades: 1,
81+
maximum_distance: 1.6,
82+
..default()
83+
}
84+
.into(),
85+
..default()
86+
});
87+
commands.spawn(SceneBundle {
88+
scene: asset_server
89+
.load(GltfAssetLabel::Scene(0).from_asset("models/GltfPrimitives/gltf_primitives.glb")),
90+
transform: Transform {
91+
rotation: Quat::from_rotation_y(-90.0 / 180.0 * PI),
92+
..default()
93+
},
94+
..default()
95+
});
96+
}

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Example | Description
159159
[Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
160160
[Percentage-closer soft shadows](../examples/3d/pcss.rs) | Demonstrates percentage-closer soft shadows (PCSS)
161161
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
162+
[Query glTF primitives](../examples/3d/query_gltf_primitives.rs) | Query primitives in a glTF scene
162163
[Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes
163164
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
164165
[Rotate Environment Map](../examples/3d/rotate_environment_map.rs) | Demonstrates how to rotate the skybox and the environment map simultaneously

0 commit comments

Comments
 (0)