From d26900a9ea7423d2375dbee06994d2629a65425e Mon Sep 17 00:00:00 2001 From: Mark Moissette Date: Mon, 3 Jun 2024 15:16:38 +0200 Subject: [PATCH] add handling of all missing gltf extras: scene, mesh & materials (#13453) # Objective - fixes #4823 ## Solution As outlined in the discussion in the linked issue as the best current solution, this PR adds specific GltfExtras for - scenes - meshes - materials - As it is , it is not a breaking change, I hesitated to rename the current "GltfExtras" component to "PrimitiveGltfExtras", but that would result in a breaking change and might be a bit confusing as to what "primitive" that refers to. ## Testing - I included a bare-bones example & asset (exported gltf file from Blender) with gltf extras at all the relevant levels : scene, mesh, material --- ## Changelog - adds "SceneGltfExtras" injected at the scene level if any - adds "MeshGltfExtras", injected at the mesh level if any - adds "MaterialGltfExtras", injected at the mesh level if any: ie if a mesh has a material that has gltf extras, the component will be injected there. --- Cargo.toml | 11 +++ assets/models/extras/gltf_extras.glb | Bin 0 -> 2200 bytes crates/bevy_gltf/src/lib.rs | 35 +++++++++- crates/bevy_gltf/src/loader.rs | 31 ++++++++- examples/3d/load_gltf_extras.rs | 97 +++++++++++++++++++++++++++ examples/README.md | 1 + 6 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 assets/models/extras/gltf_extras.glb create mode 100644 examples/3d/load_gltf_extras.rs diff --git a/Cargo.toml b/Cargo.toml index ae5f431f3b..3dfdf3ec77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -802,6 +802,17 @@ description = "Loads and renders a glTF file as a scene" category = "3D Rendering" wasm = true +[[example]] +name = "load_gltf_extras" +path = "examples/3d/load_gltf_extras.rs" +doc-scrape-examples = true + +[package.metadata.example.load_gltf_extras] +name = "Load glTF extras" +description = "Loads and renders a glTF file as a scene, including the gltf extras" +category = "3D Rendering" +wasm = true + [[example]] name = "motion_blur" path = "examples/3d/motion_blur.rs" diff --git a/assets/models/extras/gltf_extras.glb b/assets/models/extras/gltf_extras.glb new file mode 100644 index 0000000000000000000000000000000000000000..be96caeec7413916fc95d593b21ebc4f9867f901 GIT binary patch literal 2200 zcmb7E+iuf95FPI2TJF4B`y7H}ySey*Py&cT+n}aGNEHf>vq_C&Z?xVNqNq|ngTH}K zV`k%}jtOmXyPlnyGiT?r&TzE<<{|+6SOf5`0KDt8cV{d}Q?8glV?&;B87Lu{&)y$O zk%-h9Vux*R<*Lg{?1;-W5($ExvdxZ5EDe!>`F6HTnSU^2{JW9? zZf7i>b~U0qmSW7Mnlj&WFf0jTjA9)fjjHH#3bj8Ue?HL)MByY=BJPUbS01Vyq1!;h zn5Tz2jf}Z9>2b_A4TBLLbYPcVw^84yRU0+0Uai+^o>w}jO{WwGipwY%E$ZuwNpHkE zQJ?pjujGW6mP4hQYpdJ(sZ<({-EbOM+1Zu~HagA@FjP22Y0mm%D{marP^iIr zM)|2wQQd4V1jrR^W89xP(=@t6j%X#PRGf_BNJU2(VW5Z2$GAR=d6Ts%tZ!?ogEUqmV*e%+55{=nXR>eB_thqIr@r{aAYq^!o9%t1sC8lI^oC;D9LVg^3(GL() + .register_type::() + .register_type::() + .register_type::() .init_asset::() .init_asset::() .init_asset::() @@ -239,7 +242,7 @@ pub struct GltfPrimitive { pub material_extras: Option, } -/// Additional untyped data that can be present on most glTF types. +/// 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). #[derive(Clone, Debug, Reflect, Default, Component)] @@ -249,6 +252,36 @@ pub struct GltfExtras { pub value: String, } +/// Additional untyped data that can be present on most glTF types at the scene level. +/// +/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). +#[derive(Clone, Debug, Reflect, Default, Component)] +#[reflect(Component)] +pub struct GltfSceneExtras { + /// Content of the extra data. + pub value: String, +} + +/// Additional untyped data that can be present on most glTF types at the mesh level. +/// +/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). +#[derive(Clone, Debug, Reflect, Default, Component)] +#[reflect(Component)] +pub struct GltfMeshExtras { + /// Content of the extra data. + pub value: String, +} + +/// Additional untyped data that can be present on most glTF types at the material level. +/// +/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). +#[derive(Clone, Debug, Reflect, Default, Component)] +#[reflect(Component)] +pub struct GltfMaterialExtras { + /// Content of the extra data. + pub value: String, +} + /// Labels that can be used to load part of a glTF /// /// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 7d1a3a4c8c..848a6a0e7d 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1,5 +1,8 @@ -use crate::GltfAssetLabel; -use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode}; +use crate::{ + vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras, + GltfMeshExtras, GltfNode, GltfSceneExtras, +}; + #[cfg(feature = "bevy_animation")] use bevy_animation::{AnimationTarget, AnimationTargetId}; use bevy_asset::{ @@ -634,7 +637,8 @@ async fn load_gltf<'a, 'b, 'c>( let mut node_index_to_entity_map = HashMap::new(); let mut entity_to_skin_index_map = EntityHashMap::default(); let mut scene_load_context = load_context.begin_labeled_asset(); - world + + let world_root_id = world .spawn(SpatialBundle::INHERITED_IDENTITY) .with_children(|parent| { for node in scene.nodes() { @@ -659,7 +663,15 @@ async fn load_gltf<'a, 'b, 'c>( return; } } + }) + .id(); + + if let Some(extras) = scene.extras().as_ref() { + world.entity_mut(world_root_id).insert(GltfSceneExtras { + value: extras.get().to_string(), }); + } + if let Some(Err(err)) = err { return Err(err); } @@ -1270,6 +1282,7 @@ fn load_node( material: load_context.get_label_handle(&material_label), ..Default::default() }); + let target_count = primitive.morph_targets().len(); if target_count != 0 { let weights = match mesh.weights() { @@ -1300,6 +1313,18 @@ fn load_node( }); } + if let Some(extras) = mesh.extras() { + mesh_entity.insert(GltfMeshExtras { + value: extras.get().to_string(), + }); + } + + if let Some(extras) = material.extras() { + mesh_entity.insert(GltfMaterialExtras { + value: extras.get().to_string(), + }); + } + mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive))); // Mark for adding skinned mesh if let Some(skin) = gltf_node.skin() { diff --git a/examples/3d/load_gltf_extras.rs b/examples/3d/load_gltf_extras.rs new file mode 100644 index 0000000000..e32dcbd5f4 --- /dev/null +++ b/examples/3d/load_gltf_extras.rs @@ -0,0 +1,97 @@ +//! Loads and renders a glTF file as a scene, and list all the different `gltf_extras`. + +use bevy::{ + gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, + prelude::*, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, check_for_gltf_extras) + .run(); +} + +#[derive(Component)] +struct ExampleDisplay; + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + commands.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + shadows_enabled: true, + ..default() + }, + ..default() + }); + // a barebones scene containing one of each gltf_extra type + commands.spawn(SceneBundle { + scene: asset_server.load("models/extras/gltf_extras.glb#Scene0"), + ..default() + }); + + // a place to display the extras on screen + commands.spawn(( + TextBundle::from_section( + "", + TextStyle { + font_size: 18., + ..default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }), + ExampleDisplay, + )); +} + +fn check_for_gltf_extras( + gltf_extras_per_entity: Query<( + Entity, + Option<&Name>, + Option<&GltfSceneExtras>, + Option<&GltfExtras>, + Option<&GltfMeshExtras>, + Option<&GltfMaterialExtras>, + )>, + mut display: Query<&mut Text, With>, +) { + let mut gltf_extra_infos_lines: Vec = vec![]; + + for (id, name, scene_extras, extras, mesh_extras, material_extras) in + gltf_extras_per_entity.iter() + { + if scene_extras.is_some() + || extras.is_some() + || mesh_extras.is_some() + || material_extras.is_some() + { + let formatted_extras = format!( + "Extras per entity {} ('Name: {}'): + - scene extras: {:?} + - primitive extras: {:?} + - mesh extras: {:?} + - material extras: {:?} + ", + id, + name.unwrap_or(&Name::default()), + scene_extras, + extras, + mesh_extras, + material_extras + ); + gltf_extra_infos_lines.push(formatted_extras); + } + let mut display = display.single_mut(); + display.sections[0].value = gltf_extra_infos_lines.join("\n"); + } +} diff --git a/examples/README.md b/examples/README.md index ddead2b7d4..29cc5f1744 100644 --- a/examples/README.md +++ b/examples/README.md @@ -143,6 +143,7 @@ Example | Description [Lightmaps](../examples/3d/lightmaps.rs) | Rendering a scene with baked lightmaps [Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines [Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene +[Load glTF extras](../examples/3d/load_gltf_extras.rs) | Loads and renders a glTF file as a scene, including the gltf extras [Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental) [Motion Blur](../examples/3d/motion_blur.rs) | Demonstrates per-pixel motion blur [Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)