Add labels to Gltf Node and Mesh assets (#13558)

# Objective

Add labels to GltfNode and GltfMesh - they are missing from the assets
even though they are need if one wants to write a custom Gltf spawning
logic.

Eg AnimationPlayer relies on Name component of the node entities to
control the animation. There is no way to actually get names of the gltf
nodes, thus you can't manually spawn subtree from the scene and animate
it.

## Solution

- Add label field and make use of existing label creation logic to store
it there.

## Testing

- Ran all tests
- Fixed tests for node_hierarchy to use lable now

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
This commit is contained in:
Mikhail Novikov 2024-06-06 01:10:33 +02:00 committed by GitHub
parent 9a123cd3a7
commit 52215ce072
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 173 additions and 83 deletions

View file

@ -205,6 +205,12 @@ pub struct Gltf {
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfNode {
/// Index of the node inside the scene
pub index: usize,
/// Computed name for a node - either a user defined node name from gLTF or a generated name from index
pub name: String,
/// Subasset label for this node within the gLTF parent asset.
pub asset_label: GltfAssetLabel,
/// Direct children of the node.
pub children: Vec<GltfNode>,
/// Mesh of the node.
@ -215,23 +221,81 @@ pub struct GltfNode {
pub extras: Option<GltfExtras>,
}
impl GltfNode {
/// Create a node extracting name and index from glTF def
pub fn new(
node: &gltf::Node,
children: Vec<GltfNode>,
mesh: Option<Handle<GltfMesh>>,
transform: bevy_transform::prelude::Transform,
extras: Option<GltfExtras>,
) -> Self {
Self {
index: node.index(),
asset_label: GltfAssetLabel::Node(node.index()),
name: if let Some(name) = node.name() {
name.to_string()
} else {
format!("GltfNode{}", node.index())
},
children,
mesh,
transform,
extras,
}
}
}
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
/// and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfMesh {
/// Index of the mesh inside the scene
pub index: usize,
/// Computed name for a mesh - either a user defined mesh name from gLTF or a generated name from index
pub name: String,
/// Subasset label for this mesh within the gLTF parent asset.
pub asset_label: GltfAssetLabel,
/// Primitives of the glTF mesh.
pub primitives: Vec<GltfPrimitive>,
/// Additional data.
pub extras: Option<GltfExtras>,
}
impl GltfMesh {
/// Create a mesh extracting name and index from glTF def
pub fn new(
mesh: &gltf::Mesh,
primitives: Vec<GltfPrimitive>,
extras: Option<GltfExtras>,
) -> Self {
Self {
index: mesh.index(),
asset_label: GltfAssetLabel::Mesh(mesh.index()),
name: if let Some(name) = mesh.name() {
name.to_string()
} else {
format!("GltfMesh{}", mesh.index())
},
primitives,
extras,
}
}
}
/// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfPrimitive {
/// Index of the primitive inside the mesh
pub index: usize,
/// Computed name for a primitive - either a user defined primitive name from gLTF or a generated name from index
pub name: String,
/// Subasset label for this mesh within the gLTF parent asset.
pub asset_label: GltfAssetLabel,
/// Topology to be rendered.
pub mesh: Handle<Mesh>,
/// Material to apply to the `mesh`.
@ -242,6 +306,38 @@ pub struct GltfPrimitive {
pub material_extras: Option<GltfExtras>,
}
impl GltfPrimitive {
/// Create a primitive extracting name and index from glTF def
pub fn new(
gltf_mesh: &gltf::Mesh,
gltf_primitive: &gltf::Primitive,
mesh: Handle<Mesh>,
material: Option<Handle<StandardMaterial>>,
extras: Option<GltfExtras>,
material_extras: Option<GltfExtras>,
) -> Self {
GltfPrimitive {
index: gltf_primitive.index(),
name: {
let mesh_name = gltf_mesh.name().unwrap_or("Mesh");
if gltf_mesh.primitives().len() > 1 {
format!("{}.{}", mesh_name, gltf_primitive.index())
} else {
mesh_name.to_string()
}
},
asset_label: GltfAssetLabel::Primitive {
mesh: gltf_mesh.index(),
primitive: gltf_primitive.index(),
},
mesh,
material,
extras,
material_extras,
}
}
}
/// Additional untyped data that can be present on most glTF types at the primitive level.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).

View file

@ -559,25 +559,24 @@ async fn load_gltf<'a, 'b, 'c>(
});
}
let mesh = load_context.add_labeled_asset(primitive_label.to_string(), mesh);
primitives.push(super::GltfPrimitive {
mesh,
material: primitive
let mesh_handle = load_context.add_labeled_asset(primitive_label.to_string(), mesh);
primitives.push(super::GltfPrimitive::new(
&gltf_mesh,
&primitive,
mesh_handle,
primitive
.material()
.index()
.and_then(|i| materials.get(i).cloned()),
extras: get_gltf_extras(primitive.extras()),
material_extras: get_gltf_extras(primitive.material().extras()),
});
get_gltf_extras(primitive.extras()),
get_gltf_extras(primitive.material().extras()),
));
}
let handle = load_context.add_labeled_asset(
GltfAssetLabel::Mesh(gltf_mesh.index()).to_string(),
super::GltfMesh {
primitives,
extras: get_gltf_extras(gltf_mesh.extras()),
},
);
let mesh =
super::GltfMesh::new(&gltf_mesh, primitives, get_gltf_extras(gltf_mesh.extras()));
let handle = load_context.add_labeled_asset(mesh.asset_label.to_string(), mesh);
if let Some(name) = gltf_mesh.name() {
named_meshes.insert(name.into(), handle.clone());
}
@ -587,18 +586,16 @@ async fn load_gltf<'a, 'b, 'c>(
let mut nodes_intermediate = vec![];
let mut named_nodes_intermediate = HashMap::default();
for node in gltf.nodes() {
let node_label = node_label(&node);
nodes_intermediate.push((
node_label,
GltfNode {
children: vec![],
mesh: node
.mesh()
GltfNode::new(
&node,
vec![],
node.mesh()
.map(|mesh| mesh.index())
.and_then(|i| meshes.get(i).cloned()),
transform: node_transform(&node),
extras: get_gltf_extras(node.extras()),
},
.and_then(|i: usize| meshes.get(i).cloned()),
node_transform(&node),
get_gltf_extras(node.extras()),
),
node.children()
.map(|child| child.index())
.collect::<Vec<_>>(),
@ -609,7 +606,7 @@ async fn load_gltf<'a, 'b, 'c>(
}
let nodes = resolve_node_hierarchy(nodes_intermediate, load_context.path())
.into_iter()
.map(|(label, node)| load_context.add_labeled_asset(label, node))
.map(|node| load_context.add_labeled_asset(node.asset_label.to_string(), node))
.collect::<Vec<Handle<GltfNode>>>();
let named_nodes = named_nodes_intermediate
.into_iter()
@ -1530,11 +1527,6 @@ fn texture_handle_from_info(
texture_handle(load_context, &texture)
}
/// Returns the label for the `node`.
fn node_label(node: &Node) -> String {
GltfAssetLabel::Node(node.index()).to_string()
}
/// Returns the label for the `scene`.
fn scene_label(scene: &gltf::Scene) -> String {
GltfAssetLabel::Scene(scene.index()).to_string()
@ -1661,16 +1653,16 @@ async fn load_buffers(
}
fn resolve_node_hierarchy(
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
nodes_intermediate: Vec<(GltfNode, Vec<usize>)>,
asset_path: &Path,
) -> Vec<(String, GltfNode)> {
) -> Vec<GltfNode> {
let mut has_errored = false;
let mut empty_children = VecDeque::new();
let mut parents = vec![None; nodes_intermediate.len()];
let mut unprocessed_nodes = nodes_intermediate
.into_iter()
.enumerate()
.map(|(i, (label, node, children))| {
.map(|(i, (node, children))| {
for child in &children {
if let Some(parent) = parents.get_mut(*child) {
*parent = Some(i);
@ -1683,20 +1675,19 @@ fn resolve_node_hierarchy(
if children.is_empty() {
empty_children.push_back(i);
}
(i, (label, node, children))
(i, (node, children))
})
.collect::<HashMap<_, _>>();
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
let mut nodes = std::collections::HashMap::<usize, GltfNode>::new();
while let Some(index) = empty_children.pop_front() {
let (label, node, children) = unprocessed_nodes.remove(&index).unwrap();
let (node, children) = unprocessed_nodes.remove(&index).unwrap();
assert!(children.is_empty());
nodes.insert(index, (label, node));
nodes.insert(index, node);
if let Some(parent_index) = parents[index] {
let (_, parent_node, parent_children) =
unprocessed_nodes.get_mut(&parent_index).unwrap();
let (parent_node, parent_children) = unprocessed_nodes.get_mut(&parent_index).unwrap();
assert!(parent_children.remove(&index));
if let Some((_, child_node)) = nodes.get(&index) {
if let Some(child_node) = nodes.get(&index) {
parent_node.children.push(child_node.clone());
}
if parent_children.is_empty() {
@ -1996,8 +1987,11 @@ mod test {
use crate::GltfNode;
impl GltfNode {
fn empty() -> Self {
fn with_generated_name(index: usize) -> Self {
GltfNode {
index,
asset_label: crate::GltfAssetLabel::Node(index),
name: format!("l{}", index),
children: vec![],
mesh: None,
transform: bevy_transform::prelude::Transform::IDENTITY,
@ -2008,87 +2002,87 @@ mod test {
#[test]
fn node_hierarchy_single_node() {
let result = resolve_node_hierarchy(
vec![("l1".to_string(), GltfNode::empty(), vec![])],
vec![(GltfNode::with_generated_name(1), vec![])],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "l1");
assert_eq!(result[0].1.children.len(), 0);
assert_eq!(result[0].name, "l1");
assert_eq!(result[0].children.len(), 0);
}
#[test]
fn node_hierarchy_no_hierarchy() {
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![]),
("l2".to_string(), GltfNode::empty(), vec![]),
(GltfNode::with_generated_name(1), vec![]),
(GltfNode::with_generated_name(2), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0, "l1");
assert_eq!(result[0].1.children.len(), 0);
assert_eq!(result[1].0, "l2");
assert_eq!(result[1].1.children.len(), 0);
assert_eq!(result[0].name, "l1");
assert_eq!(result[0].children.len(), 0);
assert_eq!(result[1].name, "l2");
assert_eq!(result[1].children.len(), 0);
}
#[test]
fn node_hierarchy_simple_hierarchy() {
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![]),
(GltfNode::with_generated_name(1), vec![1]),
(GltfNode::with_generated_name(2), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0, "l1");
assert_eq!(result[0].1.children.len(), 1);
assert_eq!(result[1].0, "l2");
assert_eq!(result[1].1.children.len(), 0);
assert_eq!(result[0].name, "l1");
assert_eq!(result[0].children.len(), 1);
assert_eq!(result[1].name, "l2");
assert_eq!(result[1].children.len(), 0);
}
#[test]
fn node_hierarchy_hierarchy() {
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![2]),
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
("l4".to_string(), GltfNode::empty(), vec![6]),
("l5".to_string(), GltfNode::empty(), vec![]),
("l6".to_string(), GltfNode::empty(), vec![]),
("l7".to_string(), GltfNode::empty(), vec![]),
(GltfNode::with_generated_name(1), vec![1]),
(GltfNode::with_generated_name(2), vec![2]),
(GltfNode::with_generated_name(3), vec![3, 4, 5]),
(GltfNode::with_generated_name(4), vec![6]),
(GltfNode::with_generated_name(5), vec![]),
(GltfNode::with_generated_name(6), vec![]),
(GltfNode::with_generated_name(7), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 7);
assert_eq!(result[0].0, "l1");
assert_eq!(result[0].1.children.len(), 1);
assert_eq!(result[1].0, "l2");
assert_eq!(result[1].1.children.len(), 1);
assert_eq!(result[2].0, "l3");
assert_eq!(result[2].1.children.len(), 3);
assert_eq!(result[3].0, "l4");
assert_eq!(result[3].1.children.len(), 1);
assert_eq!(result[4].0, "l5");
assert_eq!(result[4].1.children.len(), 0);
assert_eq!(result[5].0, "l6");
assert_eq!(result[5].1.children.len(), 0);
assert_eq!(result[6].0, "l7");
assert_eq!(result[6].1.children.len(), 0);
assert_eq!(result[0].name, "l1");
assert_eq!(result[0].children.len(), 1);
assert_eq!(result[1].name, "l2");
assert_eq!(result[1].children.len(), 1);
assert_eq!(result[2].name, "l3");
assert_eq!(result[2].children.len(), 3);
assert_eq!(result[3].name, "l4");
assert_eq!(result[3].children.len(), 1);
assert_eq!(result[4].name, "l5");
assert_eq!(result[4].children.len(), 0);
assert_eq!(result[5].name, "l6");
assert_eq!(result[5].children.len(), 0);
assert_eq!(result[6].name, "l7");
assert_eq!(result[6].children.len(), 0);
}
#[test]
fn node_hierarchy_cyclic() {
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![0]),
(GltfNode::with_generated_name(1), vec![1]),
(GltfNode::with_generated_name(2), vec![0]),
],
PathBuf::new().as_path(),
);
@ -2100,14 +2094,14 @@ mod test {
fn node_hierarchy_missing_node() {
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![2]),
("l2".to_string(), GltfNode::empty(), vec![]),
(GltfNode::with_generated_name(1), vec![2]),
(GltfNode::with_generated_name(2), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "l2");
assert_eq!(result[0].1.children.len(), 0);
assert_eq!(result[0].name, "l2");
assert_eq!(result[0].children.len(), 0);
}
}