diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index acd0874cd9..faf25e58a7 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -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, /// Mesh of the node. @@ -215,23 +221,81 @@ pub struct GltfNode { pub extras: Option, } +impl GltfNode { + /// Create a node extracting name and index from glTF def + pub fn new( + node: &gltf::Node, + children: Vec, + mesh: Option>, + transform: bevy_transform::prelude::Transform, + extras: Option, + ) -> 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, /// Additional data. pub extras: Option, } +impl GltfMesh { + /// Create a mesh extracting name and index from glTF def + pub fn new( + mesh: &gltf::Mesh, + primitives: Vec, + extras: Option, + ) -> 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, /// Material to apply to the `mesh`. @@ -242,6 +306,38 @@ pub struct GltfPrimitive { pub material_extras: Option, } +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, + material: Option>, + extras: Option, + material_extras: Option, + ) -> 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). diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index c2026ce67f..c4426026c9 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -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::>(), @@ -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::>>(); 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)>, + nodes_intermediate: Vec<(GltfNode, Vec)>, asset_path: &Path, -) -> Vec<(String, GltfNode)> { +) -> Vec { 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::>(); - let mut nodes = std::collections::HashMap::::new(); + let mut nodes = std::collections::HashMap::::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); } }