From d7af89930b2690315666ce9b3c63f9697bfd445a Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sat, 25 May 2024 01:21:35 -0700 Subject: [PATCH] Create the basic implementation for Gltf refs. --- crates/bevy_gltf_component_refs/Cargo.toml | 5 +- crates/bevy_gltf_component_refs/src/lib.rs | 174 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 1 deletion(-) diff --git a/crates/bevy_gltf_component_refs/Cargo.toml b/crates/bevy_gltf_component_refs/Cargo.toml index f602589..9eb7718 100644 --- a/crates/bevy_gltf_component_refs/Cargo.toml +++ b/crates/bevy_gltf_component_refs/Cargo.toml @@ -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] diff --git a/crates/bevy_gltf_component_refs/src/lib.rs b/crates/bevy_gltf_component_refs/src/lib.rs index 8b13789..628111f 100644 --- a/crates/bevy_gltf_component_refs/src/lib.rs +++ b/crates/bevy_gltf_component_refs/src/lib.rs @@ -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::() + .init_resource::() + .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>); + +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 { + 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, + mut commands: Commands, + ) { + ref_map.0.clear(); + + for (entity, ref_target) in ref_targets.iter() { + commands.entity(entity).remove::(); + 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 { + target: String, + #[reflect(ignore)] + _marker: PhantomData, +} + +impl GltfRef { + fn system( + refs: Query<(Entity, &Self)>, + gltf_for_entity: GltfForEntity, + ref_map: Res, + mut commands: Commands, + ) { + for (entity, gltf_ref) in refs.iter() { + commands.entity(entity).remove::(); + 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(PhantomData); + +impl Plugin for GltfRefPlugin { + fn build(&self, app: &mut App) { + app.register_type::>().add_systems( + Update, + GltfRef::::system.in_set(GltfRefsSet::ResolveRefs), + ); + } +} + +// Manual implementation of Default for GltfRefPlugin to avoid `Default` trait +// bounds on `T`. +impl Default for GltfRefPlugin { + 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>>, + 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 { + for entity in std::iter::once(entity).chain(self.hierarchy.iter_ancestors(entity)) { + if self.gltfs.contains(entity) { + return Some(entity); + } + } + None + } +}