diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index bd87b177d5eff..86eabe8226158 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -89,9 +89,10 @@ impl<'a> LoadContext<'a> { self.labeled_assets.insert(None, asset); } - pub fn set_labeled_asset(&mut self, label: &str, asset: LoadedAsset) { + pub fn set_labeled_asset(&mut self, label: &str, asset: LoadedAsset) -> Handle { assert!(!label.is_empty()); self.labeled_assets.insert(Some(label.to_string()), asset); + self.get_handle(AssetPath::new_ref(self.path(), Some(label))) } pub fn get_handle, T: Asset>(&self, id: I) -> Handle { diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 1b0937e69a160..e21f8806c5cab 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -16,6 +16,7 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.4.0" } bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } bevy_pbr = { path = "../bevy_pbr", version = "0.4.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } @@ -25,7 +26,7 @@ bevy_math = { path = "../bevy_math", version = "0.4.0" } bevy_scene = { path = "../bevy_scene", version = "0.4.0" } # other -gltf = { version = "0.15.2", default-features = false, features = ["utils"] } +gltf = { version = "0.15.2", default-features = false, features = ["utils", "names"] } image = { version = "0.23.12", default-features = false } thiserror = "1.0" anyhow = "1.0" diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index a819b1ef75314..591d6c0701356 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -1,8 +1,14 @@ +use std::collections::HashMap; + mod loader; pub use loader::*; use bevy_app::prelude::*; -use bevy_asset::AddAsset; +use bevy_asset::{AddAsset, Handle}; +use bevy_pbr::prelude::StandardMaterial; +use bevy_reflect::TypeUuid; +use bevy_render::mesh::Mesh; +use bevy_scene::Scene; /// Adds support for GLTF file loading to Apps #[derive(Default)] @@ -10,6 +16,45 @@ pub struct GltfPlugin; impl Plugin for GltfPlugin { fn build(&self, app: &mut AppBuilder) { - app.init_asset_loader::(); + app.init_asset_loader::() + .add_asset::() + .add_asset::() + .add_asset::() + .add_asset::(); } } + +#[derive(Debug, TypeUuid)] +#[uuid = "5c7d5f8a-f7b0-4e45-a09e-406c0372fea2"] +pub struct Gltf { + pub scenes: Vec>, + pub named_scenes: HashMap>, + pub meshes: Vec>, + pub named_meshes: HashMap>, + pub materials: Vec>, + pub named_materials: HashMap>, + pub nodes: Vec>, + pub named_nodes: HashMap>, + pub default_scene: Option>, +} + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "dad74750-1fd6-460f-ac51-0a7937563865"] +pub struct GltfNode { + pub children: Vec, + pub mesh: Option>, + pub transform: bevy_transform::prelude::Transform, +} + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "8ceaec9a-926a-4f29-8ee3-578a69f42315"] +pub struct GltfMesh { + pub primitives: Vec, +} + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "cbfca302-82fd-41cb-af77-cab6b3d50af1"] +pub struct GltfPrimitive { + pub mesh: Handle, + pub material: Option>, +} diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 25bad7a423cc9..30860e2bb5350 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use bevy_asset::{AssetIoError, AssetLoader, AssetPath, LoadContext, LoadedAsset}; +use bevy_asset::{AssetIoError, AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset}; +use bevy_core::Labels; use bevy_ecs::{bevy_utils::BoxedFuture, World, WorldBuilderSource}; use bevy_math::Mat4; use bevy_pbr::prelude::{PbrBundle, StandardMaterial}; @@ -26,9 +27,11 @@ use gltf::{ Material, Primitive, }; use image::{GenericImageView, ImageFormat}; -use std::path::Path; +use std::{collections::HashMap, path::Path}; use thiserror::Error; +use crate::{Gltf, GltfNode}; + /// An error that occurs when loading a GLTF file #[derive(Error, Debug)] pub enum GltfError { @@ -75,49 +78,120 @@ async fn load_gltf<'a, 'b>( load_context: &'a mut LoadContext<'b>, ) -> Result<(), GltfError> { let gltf = gltf::Gltf::from_slice(bytes)?; - let mut world = World::default(); let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?; - let world_builder = &mut world.build(); + let mut materials = vec![]; + let mut named_materials = HashMap::new(); + for material in gltf.materials() { + let handle = load_material(&material, load_context); + if let Some(name) = material.name() { + named_materials.insert(name.to_string(), handle.clone()); + } + materials.push(handle); + } + let mut meshes = vec![]; + let mut named_meshes = HashMap::new(); for mesh in gltf.meshes() { + let mut primitives = vec![]; for primitive in mesh.primitives() { let primitive_label = primitive_label(&mesh, &primitive); - if !load_context.has_labeled_asset(&primitive_label) { - let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()])); - let primitive_topology = get_primitive_topology(primitive.mode())?; + let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()])); + let primitive_topology = get_primitive_topology(primitive.mode())?; - let mut mesh = Mesh::new(primitive_topology); + let mut mesh = Mesh::new(primitive_topology); - if let Some(vertex_attribute) = reader - .read_positions() - .map(|v| VertexAttributeValues::Float3(v.collect())) - { - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute); - } - - if let Some(vertex_attribute) = reader - .read_normals() - .map(|v| VertexAttributeValues::Float3(v.collect())) - { - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); - } + if let Some(vertex_attribute) = reader + .read_positions() + .map(|v| VertexAttributeValues::Float3(v.collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute); + } - if let Some(vertex_attribute) = reader - .read_tex_coords(0) - .map(|v| VertexAttributeValues::Float2(v.into_f32().collect())) - { - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute); - } + if let Some(vertex_attribute) = reader + .read_normals() + .map(|v| VertexAttributeValues::Float3(v.collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); + } - if let Some(indices) = reader.read_indices() { - mesh.set_indices(Some(Indices::U32(indices.into_u32().collect()))); - }; + if let Some(vertex_attribute) = reader + .read_tex_coords(0) + .map(|v| VertexAttributeValues::Float2(v.into_f32().collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute); + } - load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); + if let Some(indices) = reader.read_indices() { + mesh.set_indices(Some(Indices::U32(indices.into_u32().collect()))); }; + + let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); + primitives.push(super::GltfPrimitive { + mesh, + material: primitive + .material() + .index() + .and_then(|i| materials.get(i).cloned()), + }); + } + let handle = load_context.set_labeled_asset( + &mesh_label(&mesh), + LoadedAsset::new(super::GltfMesh { primitives }), + ); + if let Some(name) = mesh.name() { + named_meshes.insert(name.to_string(), handle.clone()); + } + meshes.push(handle); + } + + let mut nodes_intermediate = vec![]; + let mut named_nodes_intermediate = HashMap::new(); + for node in gltf.nodes() { + let node_label = node_label(&node); + nodes_intermediate.push(( + node_label, + GltfNode { + children: vec![], + mesh: node + .mesh() + .map(|mesh| mesh.index()) + .and_then(|i| meshes.get(i).cloned()), + transform: match node.transform() { + gltf::scene::Transform::Matrix { matrix } => { + Transform::from_matrix(bevy_math::Mat4::from_cols_array_2d(&matrix)) + } + gltf::scene::Transform::Decomposed { + translation, + rotation, + scale, + } => Transform { + translation: bevy_math::Vec3::from(translation), + rotation: bevy_math::Quat::from(rotation), + scale: bevy_math::Vec3::from(scale), + }, + }, + }, + node.children() + .map(|child| child.index()) + .collect::>(), + )); + if let Some(name) = node.name() { + named_nodes_intermediate.insert(name, node.index()); } } + let nodes = resolve_node_hierarchy(nodes_intermediate) + .into_iter() + .map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node))) + .collect::>>(); + let named_nodes = named_nodes_intermediate + .into_iter() + .filter_map(|(name, index)| { + nodes + .get(index) + .map(|handle| (name.to_string(), handle.clone())) + }) + .collect(); for texture in gltf.textures() { if let gltf::image::Source::View { view, mime_type } = texture.source().source() { @@ -134,7 +208,7 @@ async fn load_gltf<'a, 'b>( let image = image.into_rgba8(); let texture_label = texture_label(&texture); - load_context.set_labeled_asset( + load_context.set_labeled_asset::( &texture_label, LoadedAsset::new(Texture { data: image.clone().into_vec(), @@ -147,12 +221,12 @@ async fn load_gltf<'a, 'b>( } } - for material in gltf.materials() { - load_material(&material, load_context); - } - + let mut scenes = vec![]; + let mut named_scenes = HashMap::new(); for scene in gltf.scenes() { let mut err = None; + let mut world = World::default(); + let world_builder = &mut world.build(); world_builder .spawn((Transform::default(), GlobalTransform::default())) .with_children(|parent| { @@ -167,14 +241,34 @@ async fn load_gltf<'a, 'b>( if let Some(Err(err)) = err { return Err(err); } + let scene_handle = load_context + .set_labeled_asset(&scene_label(&scene), LoadedAsset::new(Scene::new(world))); + + if let Some(name) = scene.name() { + named_scenes.insert(name.to_string(), scene_handle.clone()); + } + scenes.push(scene_handle); } - load_context.set_default_asset(LoadedAsset::new(Scene::new(world))); + load_context.set_default_asset(LoadedAsset::new(Gltf { + default_scene: gltf + .default_scene() + .and_then(|scene| scenes.get(scene.index())) + .cloned(), + scenes, + named_scenes, + meshes, + named_meshes, + materials, + named_materials, + nodes, + named_nodes, + })); Ok(()) } -fn load_material(material: &Material, load_context: &mut LoadContext) { +fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle { let material_label = material_label(&material); let pbr = material.pbr_metallic_roughness(); let mut dependencies = Vec::new(); @@ -223,6 +317,10 @@ fn load_node( GlobalTransform::default(), )); + if let Some(name) = gltf_node.name() { + node.with(Labels::from(vec![name.to_string()])); + } + // create camera node if let Some(camera) = gltf_node.camera() { node.with(VisibleEntities { @@ -315,6 +413,10 @@ fn load_node( } } +fn mesh_label(mesh: &gltf::Mesh) -> String { + format!("Mesh{}", mesh.index()) +} + fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { format!("Mesh{}/Primitive{}", mesh.index(), primitive.index()) } @@ -331,6 +433,14 @@ fn texture_label(texture: &gltf::Texture) -> String { format!("Texture{}", texture.index()) } +fn node_label(node: &gltf::Node) -> String { + format!("Node{}", node.index()) +} + +fn scene_label(scene: &gltf::Scene) -> String { + format!("Scene{}", scene.index()) +} + fn texture_sampler(texture: &gltf::Texture) -> Result { let gltf_sampler = texture.sampler(); @@ -415,3 +525,152 @@ async fn load_buffers( Ok(buffer_data) } + +fn resolve_node_hierarchy( + nodes_intermediate: Vec<(String, GltfNode, Vec)>, +) -> Vec<(String, GltfNode)> { + let mut max_steps = nodes_intermediate.len(); + let mut nodes_step = nodes_intermediate + .into_iter() + .enumerate() + .map(|(i, (label, node, children))| (i, label, node, children)) + .collect::>(); + let mut nodes = std::collections::HashMap::::new(); + while max_steps > 0 && !nodes_step.is_empty() { + if let Some((index, label, node, _)) = nodes_step + .iter() + .find(|(_, _, _, children)| children.is_empty()) + .cloned() + { + nodes.insert(index, (label, node)); + for (_, _, node, children) in nodes_step.iter_mut() { + if let Some((i, _)) = children + .iter() + .enumerate() + .find(|(_, child_index)| **child_index == index) + { + children.remove(i); + + if let Some((_, child_node)) = nodes.get(&index) { + node.children.push(child_node.clone()) + } + } + } + nodes_step = nodes_step + .into_iter() + .filter(|(i, _, _, _)| *i != index) + .collect() + } + max_steps -= 1; + } + + let mut nodes_to_sort = nodes.into_iter().collect::>(); + nodes_to_sort.sort_by_key(|(i, _)| *i); + nodes_to_sort + .into_iter() + .map(|(_, resolved)| resolved) + .collect() +} + +#[cfg(test)] +mod test { + use super::resolve_node_hierarchy; + use crate::GltfNode; + + impl GltfNode { + fn empty() -> Self { + GltfNode { + children: vec![], + mesh: None, + transform: bevy_transform::prelude::Transform::default(), + } + } + } + #[test] + fn node_hierarchy_single_node() { + let result = resolve_node_hierarchy(vec![("l1".to_string(), GltfNode::empty(), vec![])]); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].0, "l1"); + assert_eq!(result[0].1.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![]), + ]); + + 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); + } + + #[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![]), + ]); + + 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); + } + + #[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![]), + ]); + + 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); + } + + #[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]), + ]); + + assert_eq!(result.len(), 0); + } + + #[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![]), + ]); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].0, "l2"); + assert_eq!(result[0].1.children.len(), 0); + } +} diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index e374970f3038d..ccd1e98a49d58 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -10,7 +10,7 @@ fn main() { fn setup(commands: &mut Commands, asset_server: Res) { commands - .spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf")) + .spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")) .spawn(LightBundle { transform: Transform::from_translation(Vec3::new(4.0, 5.0, 4.0)), ..Default::default() diff --git a/examples/asset/hot_asset_reloading.rs b/examples/asset/hot_asset_reloading.rs index cf542a48e9355..2209e7b2ab1c6 100644 --- a/examples/asset/hot_asset_reloading.rs +++ b/examples/asset/hot_asset_reloading.rs @@ -12,7 +12,7 @@ fn main() { fn setup(commands: &mut Commands, asset_server: Res) { // Load our mesh: - let scene_handle = asset_server.load("models/monkey/Monkey.gltf"); + let scene_handle = asset_server.load("models/monkey/Monkey.gltf#Scene0"); // Tell the asset server to watch for asset changes on disk: asset_server.watch_for_changes().unwrap(); diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index f8f91434da89f..be8f46929848f 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -185,7 +185,7 @@ fn setup_pipeline( // add entities to the world commands - .spawn_scene(asset_server.load("models/monkey/Monkey.gltf")) + .spawn_scene(asset_server.load("models/monkey/Monkey.gltf#Scene0")) // light .spawn(LightBundle { transform: Transform::from_translation(Vec3::new(4.0, 5.0, 4.0)),