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 0000000000..be96caeec7 Binary files /dev/null and b/assets/models/extras/gltf_extras.glb differ diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 1f231f0ae1..acd0874cd9 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -146,6 +146,9 @@ impl GltfPlugin { impl Plugin for GltfPlugin { fn build(&self, app: &mut App) { app.register_type::() + .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)