Create the basic implementation for Gltf refs.

This commit is contained in:
andriyDev 2024-05-25 01:21:35 -07:00
parent c6d320c573
commit d7af89930b
2 changed files with 178 additions and 1 deletions

View File

@ -14,7 +14,10 @@ license = "MIT OR Apache-2.0"
workspace = true
[dependencies]
bevy = { version = "0.13", default-features = false }
bevy = { version = "0.13", default-features = false, features = [
"bevy_asset",
"bevy_scene",
] }
bevy_gltf_components = { version = "0.5", path = "../bevy_gltf_components" }
[dev-dependencies]

View File

@ -1 +1,175 @@
use bevy::{
app::{App, Plugin},
asset::Handle,
ecs::{
component::Component,
entity::Entity,
query::With,
reflect::ReflectComponent,
schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet},
system::{Commands, Query, Res, ResMut, Resource, SystemParam},
},
hierarchy::{HierarchyQueryExt, Parent},
log::warn,
prelude::Update,
reflect::{Reflect, TypePath},
scene::Scene,
utils::HashMap,
};
use bevy_gltf_components::GltfComponentsSet;
use std::marker::PhantomData;
/// Plugin for keeping the GltfRefMap in sync.
pub struct GltfRefMapPlugin;
impl Plugin for GltfRefMapPlugin {
fn build(&self, app: &mut App) {
app.register_type::<GltfRefTarget>()
.init_resource::<GltfRefMap>()
.configure_sets(
Update,
(GltfRefsSet::CreateRefMap, GltfRefsSet::ResolveRefs)
.chain()
.after(GltfComponentsSet::Injection),
)
.add_systems(
Update,
GltfRefMap::create_ref_map.in_set(GltfRefsSet::CreateRefMap),
);
}
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
/// SystemSet to order your systems based on when references are resolved.
pub enum GltfRefsSet {
/// Systems that create the [`GltfRefMap`] for that frame.
CreateRefMap,
/// Systems that resolve references using the [`GltfRefMap`]. For types that
/// need to have references manually resolved, prefer to put their systems
/// here.
ResolveRefs,
}
#[derive(Component, Reflect)]
#[reflect(Component)]
struct GltfRefTarget(String);
/// A map of references for Gltf instances.
#[derive(Resource, Default)]
pub struct GltfRefMap(HashMap<Entity, HashMap<String, Entity>>);
impl GltfRefMap {
/// Gets an entity given its `gltf_root` and `ref_name`. A reference is
/// only valid on the same frame that the Gltf is spawned in. In other
/// words, after that same frame, any references on `gltf_root` will return
/// None.
pub fn get_ref(&self, gltf_root: Entity, ref_name: &String) -> Option<Entity> {
self.0.get(&gltf_root)?.get(ref_name).copied()
}
fn create_ref_map(
ref_targets: Query<(Entity, &GltfRefTarget)>,
gltf_for_entity: GltfForEntity,
mut ref_map: ResMut<GltfRefMap>,
mut commands: Commands,
) {
ref_map.0.clear();
for (entity, ref_target) in ref_targets.iter() {
commands.entity(entity).remove::<GltfRefTarget>();
let Some(gltf_root) = gltf_for_entity.find_gltf_root(entity) else {
warn!("Entity {entity:?} is not part of a Gltf but contains a GltfRefTarget (target={}).", ref_target.0);
continue;
};
ref_map
.0
.entry(gltf_root)
.or_default()
.insert(ref_target.0.clone(), entity);
}
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
struct GltfRef<T: Component + FromGltfRef> {
target: String,
#[reflect(ignore)]
_marker: PhantomData<T>,
}
impl<T: Component + FromGltfRef> GltfRef<T> {
fn system(
refs: Query<(Entity, &Self)>,
gltf_for_entity: GltfForEntity,
ref_map: Res<GltfRefMap>,
mut commands: Commands,
) {
for (entity, gltf_ref) in refs.iter() {
commands.entity(entity).remove::<Self>();
let Some(gltf_root) = gltf_for_entity.find_gltf_root(entity) else {
warn!("GltfRef should only be on descendants of a Gltf.");
continue;
};
match ref_map.get_ref(gltf_root, &gltf_ref.target) {
Some(target) => {
commands.entity(entity).insert(T::from_ref(target));
}
None => {
warn!(
"Entity {entity:?} attempted to reference '{}' in gltf {gltf_root:?}",
&gltf_ref.target
);
}
}
}
}
}
/// Trait for creating a component for a Gltf reference.
pub trait FromGltfRef {
/// Creates `Self` given the `entity` for this reference.
fn from_ref(entity: Entity) -> Self;
}
/// Plugin for automatically converting [`GltfRef`]s into their corresponding
/// `T`.
pub struct GltfRefPlugin<T: Component + FromGltfRef>(PhantomData<T>);
impl<T: Component + FromGltfRef + Reflect + TypePath> Plugin for GltfRefPlugin<T> {
fn build(&self, app: &mut App) {
app.register_type::<GltfRef<T>>().add_systems(
Update,
GltfRef::<T>::system.in_set(GltfRefsSet::ResolveRefs),
);
}
}
// Manual implementation of Default for GltfRefPlugin to avoid `Default` trait
// bounds on `T`.
impl<T: Component + FromGltfRef> Default for GltfRefPlugin<T> {
fn default() -> Self {
Self(Default::default())
}
}
/// SystemParam to find the Gltf that an entity belongs to.
#[derive(SystemParam)]
pub struct GltfForEntity<'w, 's> {
gltfs: Query<'w, 's, (), With<Handle<Scene>>>,
hierarchy: Query<'w, 's, &'static Parent>,
}
impl<'w, 's> GltfForEntity<'w, 's> {
/// Finds the Gltf root for `entity`. Returns None if `entity` does not
/// belong to a Gltf.
pub fn find_gltf_root(&self, entity: Entity) -> Option<Entity> {
for entity in std::iter::once(entity).chain(self.hierarchy.iter_ancestors(entity)) {
if self.gltfs.contains(entity) {
return Some(entity);
}
}
None
}
}