mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
9a123cd3a7
commit
52215ce072
2 changed files with 173 additions and 83 deletions
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue