feat(bevy_gltf_components): testing a different approach using only gltf extras (#134)

* not using / tracking gltf files anymore (should avoid issues with scenes vs gltf files)
 * restructured accordingly
 * closes #102
 * closes #111
This commit is contained in:
Mark Moissette 2024-02-19 22:43:27 +01:00 committed by GitHub
parent e0e3a620f7
commit 1ceb5050f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 94 additions and 230 deletions

View File

@ -113,6 +113,7 @@ Thanks to all the contributors helping out with this project ! Big kudos to you,
- [BSDGuyShawn](https://github.com/BSDGuyShawn) - [BSDGuyShawn](https://github.com/BSDGuyShawn)
- [yukkop](https://github.com/yukkop) - [yukkop](https://github.com/yukkop)
- [killercup](https://github.com/killercup) - [killercup](https://github.com/killercup)
- [janhohenheim ](https://github.com/janhohenheim)
## License ## License

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_gltf_blueprints" name = "bevy_gltf_blueprints"
version = "0.7.1" version = "0.7.3"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy." description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_gltf_components" name = "bevy_gltf_components"
version = "0.3.1" version = "0.3.2"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side." description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -1,8 +1,8 @@
pub mod utils; pub mod utils;
pub use utils::*; pub use utils::*;
pub mod gltf_to_components; pub mod ronstring_to_reflect_component;
pub use gltf_to_components::*; pub use ronstring_to_reflect_component::*;
pub mod process_gltfs; pub mod process_gltfs;
pub use process_gltfs::*; pub use process_gltfs::*;
@ -68,14 +68,12 @@ impl Default for ComponentsFromGltfPlugin {
impl Plugin for ComponentsFromGltfPlugin { impl Plugin for ComponentsFromGltfPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(GltfLoadingTracker::new()) app.insert_resource(GltfComponentsConfig {
.insert_resource(GltfComponentsConfig { legacy_mode: self.legacy_mode,
legacy_mode: self.legacy_mode, })
}) .add_systems(
.add_systems(Update, (track_new_gltf, process_loaded_scenes)) Update,
.add_systems( (add_components_from_gltf_extras).in_set(GltfComponentsSet::Injection),
Update, );
(process_loaded_scenes).in_set(GltfComponentsSet::Injection),
);
} }
} }

View File

@ -1,101 +1,87 @@
use bevy::asset::AssetPath; use bevy::{
use bevy::gltf::Gltf; core::Name,
use bevy::utils::HashSet; ecs::{
use bevy::{asset::LoadState, prelude::*}; entity::Entity,
use std::path::Path; query::Added,
reflect::{AppTypeRegistry, ReflectComponent},
world::World,
},
gltf::GltfExtras,
hierarchy::Parent,
log::debug,
reflect::{Reflect, TypeRegistration},
utils::HashMap,
};
use crate::{gltf_extras_to_components, GltfComponentsConfig}; use crate::{ronstring_to_reflect_component, GltfComponentsConfig};
#[derive(Resource)] /// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
/// component to keep track of gltfs' loading state pub fn add_components_from_gltf_extras(world: &mut World) {
pub struct GltfLoadingTracker { let mut extras =
pub loading_gltfs: HashSet<Handle<Gltf>>, world.query_filtered::<(Entity, &Name, &GltfExtras, &Parent), Added<GltfExtras>>();
pub processed_gltfs: HashSet<String>, let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> =
} HashMap::new();
impl Default for GltfLoadingTracker {
fn default() -> Self {
Self::new()
}
}
impl GltfLoadingTracker { let gltf_components_config = world.resource::<GltfComponentsConfig>();
pub fn new() -> GltfLoadingTracker {
GltfLoadingTracker {
loading_gltfs: HashSet::new(),
processed_gltfs: HashSet::new(),
}
}
pub fn add_gltf(&mut self, handle: Handle<Gltf>) {
self.loading_gltfs.insert(handle);
}
}
pub fn track_new_gltf( for (entity, name, extra, parent) in extras.iter(world) {
mut tracker: ResMut<GltfLoadingTracker>,
mut events: EventReader<AssetEvent<Gltf>>,
asset_server: Res<AssetServer>,
) {
for event in events.read() {
if let AssetEvent::Added { id } = event {
let handle = asset_server.get_id_handle(*id);
if let Some(handle) = handle {
tracker.add_gltf(handle.clone());
debug!("gltf created {:?}", handle);
} else {
let asset_path = asset_server
.get_path(*id)
.unwrap_or(AssetPath::from_path(Path::new("n/a"))); // will unfortunatly not work, will do a PR/ discussion at the Bevy level, leaving for reference, would be very practical
warn!(
"a gltf file ({:?}) has no handle available, cannot inject components",
asset_path
);
}
}
}
events.clear();
}
pub fn process_loaded_scenes(
mut gltfs: ResMut<Assets<Gltf>>,
mut tracker: ResMut<GltfLoadingTracker>,
mut scenes: ResMut<Assets<Scene>>,
app_type_registry: Res<AppTypeRegistry>,
asset_server: Res<AssetServer>,
gltf_components_config: Res<GltfComponentsConfig>,
) {
let mut loaded_gltfs = Vec::new();
for gltf in &tracker.loading_gltfs {
debug!( debug!(
"checking for loaded gltfs {:?}", "Name: {}, entity {:?}, parent: {:?}, extras {:?}",
asset_server.get_load_state(gltf) name, entity, parent, extra
); );
if let Some(load_state) = asset_server.get_load_state(gltf.clone()) { let type_registry: &AppTypeRegistry = world.resource();
if load_state == LoadState::Loaded { let type_registry = type_registry.read();
debug!("Adding scene to processing list"); let reflect_components = ronstring_to_reflect_component(
loaded_gltfs.push(gltf.clone()); &extra.value,
&type_registry,
gltf_components_config.legacy_mode,
);
// we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity;
// if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent
// this is mostly used for Blender collections
if name.as_str().contains("components") || name.as_str().ends_with("_pa") {
debug!("adding components to parent");
target_entity = parent.get();
}
debug!("adding to {:?}", target_entity);
// if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly
// this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values
if entity_components.contains_key(&target_entity) {
let mut updated_components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
let current_components = &entity_components[&target_entity];
// first inject the current components
for (component, type_registration) in current_components {
updated_components.push((component.clone_value(), type_registration.clone()));
} }
// then inject the new components: this also enables overwrite components set in the collection
for (component, type_registration) in reflect_components {
updated_components.push((component.clone_value(), type_registration));
}
entity_components.insert(target_entity, updated_components);
} else {
entity_components.insert(target_entity, reflect_components);
} }
} }
let type_registry = app_type_registry.read(); for (entity, components) in entity_components {
if !components.is_empty() {
for gltf_handle in &loaded_gltfs { debug!("--entity {:?}, components {}", entity, components.len());
if let Some(gltf) = gltfs.get_mut(gltf_handle) { }
gltf_extras_to_components( for (component, type_registration) in components {
gltf, let mut entity_mut = world.entity_mut(entity);
&mut scenes, debug!(
&*type_registry, "------adding {} {:?}",
gltf_components_config.legacy_mode, component.get_represented_type_info().unwrap().type_path(),
); component
);
if let Some(path) = gltf_handle.path() { type_registration
tracker.processed_gltfs.insert(path.to_string()); .data::<ReflectComponent>()
} .unwrap()
} else { .insert(&mut entity_mut, &*component); // TODO: how can we insert any additional components "by hand" here ?
warn!("could not find gltf asset, cannot process it");
} }
tracker.loading_gltfs.remove(gltf_handle);
debug!("Done loading gltf file");
} }
} }

View File

@ -1,37 +1,19 @@
use bevy::ecs::component::Component; use bevy::log::{debug, warn};
use bevy::render::color::Color; use bevy::reflect::serde::UntypedReflectDeserializer;
use core::ops::Deref; use bevy::reflect::{Reflect, TypeInfo, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap;
use ron::Value; use ron::Value;
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
use bevy::ecs::{entity::Entity, reflect::ReflectComponent};
use bevy::gltf::{Gltf, GltfExtras};
use bevy::reflect::serde::UntypedReflectDeserializer;
use bevy::reflect::{Reflect, TypeInfo, TypeRegistry};
use bevy::scene::Scene;
use bevy::utils::HashMap;
use bevy::{
log::{debug, warn},
prelude::{Assets, Name, Parent, ResMut},
};
use super::capitalize_first_letter; use super::capitalize_first_letter;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleTestColor(Color);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct VecOfColors(Vec<Color>);
pub fn ronstring_to_reflect_component( pub fn ronstring_to_reflect_component(
ron_string: &str, ron_string: &str,
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
simplified_types: bool, simplified_types: bool,
) -> Vec<Box<dyn Reflect>> { ) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<Box<dyn Reflect>> = Vec::new(); let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
for (key, value) in lookup.into_iter() { for (key, value) in lookup.into_iter() {
let type_string = key.replace("component: ", "").trim().to_string(); let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str()); let capitalized_type_name = capitalize_first_letter(type_string.as_str());
@ -111,7 +93,8 @@ pub fn ronstring_to_reflect_component(
} }
} }
// println!("parsed value {}",parsed_value); // FIXME: waaait this should be part of the legacy mode as it modifies the ron data ???
// so it means the values generated by the Blende add-on are incomplete (trivial to fix, but still)
if parsed_value.is_empty() { if parsed_value.is_empty() {
parsed_value = "()".to_string(); parsed_value = "()".to_string();
} }
@ -144,7 +127,7 @@ pub fn ronstring_to_reflect_component(
debug!("component {:?}", component); debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info()); debug!("real type {:?}", component.get_represented_type_info());
components.push(component); components.push((component, type_registration.clone()));
debug!("found type registration for {}", capitalized_type_name); debug!("found type registration for {}", capitalized_type_name);
} else { } else {
warn!("no type registration for {}", capitalized_type_name); warn!("no type registration for {}", capitalized_type_name);
@ -152,107 +135,3 @@ pub fn ronstring_to_reflect_component(
} }
components components
} }
/// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
pub fn gltf_extras_to_components(
gltf: &mut Gltf,
scenes: &mut ResMut<Assets<Scene>>,
type_registry: impl Deref<Target = TypeRegistry>,
legacy_mode: bool,
) {
let mut added_components = 0;
let simplified_types = legacy_mode;
if simplified_types {
warn!("using simplified component definitions is deprecated since 0.3, prefer defining components with real ron values (use the bevy_components tool for Blender for simplicity) ");
}
for (_name, scene) in &gltf.named_scenes {
debug!("gltf: scene name {:?}", _name);
let scene = scenes.get_mut(scene).unwrap();
let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>();
let mut entity_components: HashMap<Entity, Vec<Box<dyn Reflect>>> = HashMap::new();
for (entity, name, extras, parent) in query.iter(&scene.world) {
debug!("Name: {}, entity {:?}, parent: {:?}", name, entity, parent);
let reflect_components =
ronstring_to_reflect_component(&extras.value, &type_registry, simplified_types);
added_components = reflect_components.len();
debug!("Found components {}", added_components);
// we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity;
// if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent
// this is mostly used for Blender collections
if name.as_str().contains("components") || name.as_str().ends_with("_pa") {
debug!("adding components to parent");
target_entity = parent.get();
}
debug!("adding to {:?}", target_entity);
// if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly
// this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values
if entity_components.contains_key(&target_entity) {
let mut updated_components: Vec<Box<dyn Reflect>> = Vec::new();
let current_components = &entity_components[&target_entity];
// first inject the current components
for component in current_components {
updated_components.push(component.clone_value());
}
// then inject the new components: this also enables overwrite components set in the collection
for component in reflect_components {
updated_components.push(component.clone_value());
}
entity_components.insert(target_entity, updated_components);
} else {
entity_components.insert(target_entity, reflect_components);
}
// shorthand, did not manage to get it working
/* entity_components.insert(
target_entity,
if entity_components.contains_key(&target_entity) {
entity_components[&target_entity].push(reflect_components) } else { reflect_components }
);*/
debug!("-----value {:?}", &extras.value);
}
// GltfNode
// find a way to link this name to the current entity ? => WOULD BE VERY USEFULL for animations & co !!
debug!("done pre-processing components, now adding them to entities");
for (entity, components) in entity_components {
if !components.is_empty() {
debug!("--entity {:?}, components {}", entity, components.len());
}
for component in components {
let mut entity_mut = scene.world.entity_mut(entity);
debug!(
"------adding {} {:?}",
component.get_represented_type_info().unwrap().type_path(),
component
);
let component_type_path =
component.get_represented_type_info().unwrap().type_path();
type_registry
.get_with_type_path(component_type_path)
.unwrap() // Component was successfully deserialized, it has to be in the registry
.data::<ReflectComponent>()
.unwrap() // Hopefully, the component deserializer ensures those are components
.insert(&mut entity_mut, &*component);
// debug!("all components {:?}", scene.world.entity(entity).archetype().components());
// scene.world.components().
// TODO: how can we insert any additional components "by hand" here ?
}
// let entity_mut = scene.world.entity_mut(entity);
// let archetype = entity_mut.archetype().clone();
// let _all_components = archetype.components();
if added_components > 0 {
debug!("------done adding {} components", added_components);
}
}
}
debug!("done injecting components from gltf_extras");
}