diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 0ab68da7c8788..9b9e8de991af9 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -11,11 +11,12 @@ use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, prelude::Component, + query::With, reflect::ReflectComponent, schedule::IntoSystemDescriptor, system::{Query, Res}, }; -use bevy_hierarchy::Children; +use bevy_hierarchy::{Children, Parent}; use bevy_math::{Quat, Vec3}; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; use bevy_time::Time; @@ -63,17 +64,34 @@ pub struct EntityPath { #[derive(Reflect, FromReflect, Clone, TypeUuid, Debug, Default)] #[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"] pub struct AnimationClip { - curves: HashMap>, + curves: Vec>, + paths: HashMap, duration: f32, } impl AnimationClip { #[inline] - /// Hashmap of the [`VariableCurve`]s per [`EntityPath`]. - pub fn curves(&self) -> &HashMap> { + /// [`VariableCurve`]s for each bone. Indexed by the bone ID. + pub fn curves(&self) -> &Vec> { &self.curves } + /// Gets the curves for a bone. + /// + /// Returns `None` if the bone is invalid. + #[inline] + pub fn get_curves(&self, bone_id: usize) -> Option<&'_ Vec> { + self.curves.get(bone_id) + } + + /// Gets the curves by it's [`EntityPath`]. + /// + /// Returns `None` if the bone is invalid. + #[inline] + pub fn get_curves_by_path(&self, path: &EntityPath) -> Option<&'_ Vec> { + self.paths.get(path).and_then(|id| self.curves.get(*id)) + } + /// Duration of the clip, represented in seconds #[inline] pub fn duration(&self) -> f32 { @@ -86,7 +104,13 @@ impl AnimationClip { self.duration = self .duration .max(*curve.keyframe_timestamps.last().unwrap_or(&0.0)); - self.curves.entry(path).or_default().push(curve); + if let Some(bone_id) = self.paths.get(&path) { + self.curves[*bone_id].push(curve); + } else { + let idx = self.curves.len(); + self.curves.push(vec![curve]); + self.paths.insert(path, idx); + } } } @@ -99,6 +123,7 @@ pub struct AnimationPlayer { speed: f32, elapsed: f32, animation_clip: Handle, + path_cache: Vec>>, } impl Default for AnimationPlayer { @@ -109,6 +134,7 @@ impl Default for AnimationPlayer { speed: 1.0, elapsed: 0.0, animation_clip: Default::default(), + path_cache: Vec::new(), } } } @@ -181,117 +207,180 @@ impl AnimationPlayer { } } +fn find_bone( + root: Entity, + path: &EntityPath, + children: &Query<&Children>, + names: &Query<&Name>, + path_cache: &mut Vec>, +) -> Option { + // PERF: finding the target entity can be optimised + let mut current_entity = root; + path_cache.resize(path.parts.len(), None); + // Ignore the first name, it is the root node which we already have + for (idx, part) in path.parts.iter().enumerate().skip(1) { + let mut found = false; + let children = children.get(current_entity).ok()?; + if let Some(cached) = path_cache[idx] { + if children.contains(&cached) { + if let Ok(name) = names.get(cached) { + if name == part { + current_entity = cached; + found = true; + } + } + } + } + if !found { + for child in children.deref() { + if let Ok(name) = names.get(*child) { + if name == part { + // Found a children with the right name, continue to the next part + current_entity = *child; + path_cache[idx] = Some(*child); + found = true; + break; + } + } + } + } + if !found { + warn!("Entity not found for path {:?} on part {:?}", path, part); + return None; + } + } + Some(current_entity) +} + +/// Verify that there are no ancestors of a given entity that have an `AnimationPlayer`. +fn verify_no_ancestor_player( + player_parent: Option<&Parent>, + parents: &Query<(Option>, Option<&Parent>)>, +) -> bool { + let Some(mut current) = player_parent.map(Parent::get) else { return true }; + loop { + let Ok((maybe_player, parent)) = parents.get(current) else { return true }; + if maybe_player.is_some() { + return false; + } + if let Some(parent) = parent { + current = parent.get(); + } else { + return true; + } + } +} + /// System that will play all animations, using any entity with a [`AnimationPlayer`] /// and a [`Handle`] as an animation root pub fn animation_player( time: Res