diff --git a/fyrox-animation/Cargo.toml b/fyrox-animation/Cargo.toml index 0deebdb89..ae3b6d0aa 100644 --- a/fyrox-animation/Cargo.toml +++ b/fyrox-animation/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.72" [dependencies] fyrox-core = { version = "0.28.1", path = "../fyrox-core" } +fyrox-resource = { version = "0.12.0", path = "../fyrox-resource" } strum = "0.26.1" strum_macros = "0.26.1" fxhash = "0.2.1" diff --git a/fyrox-animation/src/lib.rs b/fyrox-animation/src/lib.rs index c6369315b..f7e59b0e5 100644 --- a/fyrox-animation/src/lib.rs +++ b/fyrox-animation/src/lib.rs @@ -28,29 +28,33 @@ use crate::{ core::{ algebra::{UnitQuaternion, Vector3}, math::wrapf, - pool::{Handle, Pool, Ticket}, + pool::{ErasedHandle, Handle, Pool, Ticket}, reflect::prelude::*, - uuid::Uuid, + type_traits::prelude::*, + uuid::{uuid, Uuid}, visitor::{Visit, VisitResult, Visitor}, + ImmutableString, NameProvider, }, track::Track, }; -use core::ImmutableString; -use fyrox_core::{NameProvider, TypeUuidProvider}; -use std::hash::Hash; +use fxhash::FxHashMap; +use fyrox_resource::{Resource, ResourceData}; use std::{ + any::Any, collections::VecDeque, + error::Error, fmt::Debug, + hash::Hash, ops::{Index, IndexMut, Range}, + path::Path, }; +use value::{nlerp, TrackValue, ValueBinding}; +use crate::track::TrackBinding; pub use fyrox_core as core; -use fyrox_core::pool::ErasedHandle; -use fyrox_core::uuid::uuid; - +use fyrox_resource::untyped::ResourceKind; pub use pose::{AnimationPose, NodePose}; pub use signal::{AnimationEvent, AnimationSignal}; -use value::{nlerp, TrackValue, ValueBinding}; pub mod container; pub mod machine; @@ -60,6 +64,85 @@ pub mod spritesheet; pub mod track; pub mod value; +#[derive(Default, Debug, Reflect, Clone, PartialEq, TypeUuidProvider)] +#[type_uuid(id = "044d9f7c-5c6c-4b29-8de9-d0d975a48256")] +pub struct AnimationData { + pub tracks: Vec, +} + +impl AnimationData { + /// Adds new track to the animation. Animation can have unlimited number of tracks, each track is responsible + /// for animation of a single scene node. + pub fn add_track(&mut self, track: Track) { + self.tracks.push(track); + } + + /// Removes a track at given index. + pub fn remove_track(&mut self, index: usize) -> Track { + self.tracks.remove(index) + } + + /// Inserts a track at given index. + pub fn insert_track(&mut self, index: usize, track: Track) { + self.tracks.insert(index, track) + } + + /// Removes last track from the list of tracks of the animation. + pub fn pop_track(&mut self) -> Option { + self.tracks.pop() + } + + /// Returns a reference to tracks container. + pub fn tracks(&self) -> &[Track] { + &self.tracks + } + + /// Returns a mutable reference to the track container. + pub fn tracks_mut(&mut self) -> &mut [Track] { + &mut self.tracks + } + + /// Removes all tracks from the animation for which the given `filter` closure returns `false`. Could be useful + /// to remove undesired animation tracks. + pub fn retain_tracks(&mut self, filter: F) + where + F: FnMut(&Track) -> bool, + { + self.tracks.retain(filter) + } +} + +impl Visit for AnimationData { + fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { + self.tracks.visit(name, visitor) + } +} + +impl ResourceData for AnimationData { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn type_uuid(&self) -> Uuid { + ::type_uuid() + } + + fn save(&mut self, _path: &Path) -> Result<(), Box> { + // TODO + Ok(()) + } + + fn can_be_saved(&self) -> bool { + true + } +} + +pub type AnimationDataResource = Resource; + /// # Overview /// /// Animation allows you to change properties of arbitrary entities at runtime using a set of key frames. Animation @@ -188,7 +271,8 @@ pub mod value; pub struct Animation { #[visit(optional)] name: ImmutableString, - tracks: Vec>, + tracks_data: AnimationDataResource, + track_bindings: FxHashMap>, time_position: f32, #[visit(optional)] time_slice: Range, @@ -224,7 +308,7 @@ impl TypeUuidProvider for Animation { /// Identifier of an entity, that can be animated. pub trait EntityId: - Default + Copy + Reflect + Visit + PartialEq + Eq + Hash + Debug + Ord + PartialEq + 'static + Default + Send + Copy + Reflect + Visit + PartialEq + Eq + Hash + Debug + Ord + PartialEq + 'static { } @@ -284,7 +368,7 @@ impl Clone for Animation { fn clone(&self) -> Self { Self { name: self.name.clone(), - tracks: self.tracks.clone(), + tracks_data: self.tracks_data.clone(), speed: self.speed, time_position: self.time_position, looped: self.looped, @@ -296,6 +380,7 @@ impl Clone for Animation { time_slice: self.time_slice.clone(), root_motion: self.root_motion.clone(), max_event_capacity: 32, + track_bindings: self.track_bindings.clone(), } } } @@ -321,25 +406,8 @@ impl Animation { self.name.as_ref() } - /// Adds new track to the animation. Animation can have unlimited number of tracks, each track is responsible - /// for animation of a single scene node. - pub fn add_track(&mut self, track: Track) { - self.tracks.push(track); - } - - /// Removes a track at given index. - pub fn remove_track(&mut self, index: usize) -> Track { - self.tracks.remove(index) - } - - /// Inserts a track at given index. - pub fn insert_track(&mut self, index: usize, track: Track) { - self.tracks.insert(index, track) - } - - /// Removes last track from the list of tracks of the animation. - pub fn pop_track(&mut self) -> Option> { - self.tracks.pop() + pub fn tracks_data(&self) -> &AnimationDataResource { + &self.tracks_data } /// Calculates new length of the animation based on the content of its tracks. It looks for the most "right" @@ -347,19 +415,18 @@ impl Animation { /// in case if you formed animation from code using just curves and don't know the actual length of the /// animation. pub fn fit_length_to_content(&mut self) { + let state = self.tracks_data.state(); + let Some(tracks_data) = state.data_ref() else { + return; + }; self.time_slice.start = 0.0; - for track in self.tracks.iter_mut() { + for track in tracks_data.tracks.iter() { if track.time_length() > self.time_slice.end { self.time_slice.end = track.time_length(); } } } - /// Returns a reference to tracks container. - pub fn tracks(&self) -> &[Track] { - &self.tracks - } - /// Sets new time position of the animation. The actual time position the animation will have after the call, /// can be different in two reasons: /// @@ -436,7 +503,13 @@ impl Animation { } fn update_root_motion(&mut self, prev_time_position: f32) { - fn fetch_position_at_time(tracks: &[Track], time: f32) -> Vector3 { + let state = self.tracks_data.state(); + let Some(tracks_data) = state.data_ref() else { + return; + }; + let tracks = &tracks_data.tracks; + + fn fetch_position_at_time(tracks: &[Track], time: f32) -> Vector3 { tracks .iter() .find(|track| track.binding() == &ValueBinding::Position) @@ -451,10 +524,7 @@ impl Animation { .unwrap_or_default() } - fn fetch_rotation_at_time( - tracks: &[Track], - time: f32, - ) -> UnitQuaternion { + fn fetch_rotation_at_time(tracks: &[Track], time: f32) -> UnitQuaternion { tracks .iter() .find(|track| track.binding() == &ValueBinding::Rotation) @@ -499,9 +569,9 @@ impl Animation { if let TrackValue::Vector3(pose_position) = bound_value.value { if new_loop_cycle_started { root_motion.prev_position = - fetch_position_at_time(&self.tracks, cycle_start_time); + fetch_position_at_time(tracks, cycle_start_time); root_motion.position_offset_remainder = Some( - fetch_position_at_time(&self.tracks, cycle_end_time) + fetch_position_at_time(tracks, cycle_end_time) - pose_position, ); } else { @@ -536,7 +606,7 @@ impl Animation { // Reset position so the root won't move. let start_position = - fetch_position_at_time(&self.tracks, self.time_slice.start); + fetch_position_at_time(tracks, self.time_slice.start); bound_value.value = TrackValue::Vector3(Vector3::new( if root_motion_settings.ignore_x_movement { @@ -562,9 +632,9 @@ impl Animation { if !root_motion_settings.ignore_rotations { if new_loop_cycle_started { root_motion.prev_rotation = - fetch_rotation_at_time(&self.tracks, cycle_start_time); + fetch_rotation_at_time(tracks, cycle_start_time); root_motion.rotation_remainder = Some( - fetch_rotation_at_time(&self.tracks, cycle_end_time) + fetch_rotation_at_time(tracks, cycle_end_time) .inverse() * pose_rotation, ); @@ -584,7 +654,7 @@ impl Animation { // Reset rotation so the root won't rotate. bound_value.value = TrackValue::UnitQuaternion( - fetch_rotation_at_time(&self.tracks, self.time_slice.start), + fetch_rotation_at_time(tracks, self.time_slice.start), ); } } @@ -686,9 +756,20 @@ impl Animation { self.enabled } - /// Returns a mutable reference to the track container. - pub fn tracks_mut(&mut self) -> &mut [Track] { - &mut self.tracks + pub fn add_track(&mut self, target: T, track: Track) { + let mut state = self.tracks_data.state(); + let Some(tracks_data) = state.data() else { + return; + }; + let id = track.id(); + tracks_data.tracks.push(track); + self.track_bindings.insert( + id, + TrackBinding { + enabled: true, + target, + }, + ); } /// Adds a new animation signal to the animation. See [`AnimationSignal`] docs for more info and examples. @@ -722,36 +803,21 @@ impl Animation { &mut self.signals } - /// Removes all tracks from the animation for which the given `filter` closure returns `false`. Could be useful - /// to remove undesired animation tracks. - pub fn retain_tracks(&mut self, filter: F) - where - F: FnMut(&Track) -> bool, - { - self.tracks.retain(filter) - } - /// Tries to find all tracks that refer to a given node and enables or disables them. pub fn set_node_track_enabled(&mut self, handle: T, enabled: bool) { - for track in self.tracks.iter_mut() { + for track in self.track_bindings.values_mut() { if track.target() == handle { track.set_enabled(enabled); } } } - /// Returns an iterator that yields a number of references to tracks that refer to a given node. - pub fn tracks_of(&self, handle: T) -> impl Iterator> { - self.tracks - .iter() - .filter(move |track| track.target() == handle) + pub fn track_bindings(&self) -> &FxHashMap> { + &self.track_bindings } - /// Returns an iterator that yields a number of references to tracks that refer to a given node. - pub fn tracks_of_mut(&mut self, handle: T) -> impl Iterator> { - self.tracks - .iter_mut() - .filter(move |track| track.target() == handle) + pub fn track_bindings_mut(&mut self) -> &mut FxHashMap> { + &mut self.track_bindings } /// Tries to find a layer by its name. Returns index of the signal and its reference. @@ -781,15 +847,24 @@ impl Animation { /// Removes all tracks from the animation. pub fn remove_tracks(&mut self) { - self.tracks.clear(); + self.track_bindings.clear(); } fn update_pose(&mut self) { + let state = self.tracks_data.state(); + let Some(tracks_data) = state.data_ref() else { + return; + }; + self.pose.reset(); - for track in self.tracks.iter() { - if track.is_enabled() { + for track in tracks_data.tracks.iter() { + let Some(binding) = self.track_bindings.get(&track.id()) else { + continue; + }; + + if binding.is_enabled() { if let Some(bound_value) = track.fetch(self.time_position) { - self.pose.add_to_node_pose(track.target(), bound_value); + self.pose.add_to_node_pose(binding.target(), bound_value); } } } @@ -805,7 +880,7 @@ impl Default for Animation { fn default() -> Self { Self { name: Default::default(), - tracks: Vec::new(), + tracks_data: Resource::new_ok(ResourceKind::Embedded, AnimationData::default()), speed: 1.0, time_position: 0.0, enabled: true, @@ -817,6 +892,7 @@ impl Default for Animation { time_slice: Default::default(), root_motion: None, max_event_capacity: 32, + track_bindings: Default::default(), } } } diff --git a/fyrox-animation/src/track.rs b/fyrox-animation/src/track.rs index dc77ebb26..5d6bb3e58 100644 --- a/fyrox-animation/src/track.rs +++ b/fyrox-animation/src/track.rs @@ -28,25 +28,63 @@ use crate::{ }; use std::fmt::Debug; +#[derive(Debug, Visit, Reflect, Clone, PartialEq)] +pub struct TrackBinding { + pub enabled: bool, + pub target: T, +} + +impl Default for TrackBinding { + fn default() -> Self { + Self { + enabled: true, + target: Default::default(), + } + } +} + +impl TrackBinding { + /// Sets a handle of a node that will be animated. + pub fn set_target(&mut self, target: T) { + self.target = target; + } + + /// Returns a handle of a node that will be animated. + pub fn target(&self) -> T { + self.target + } + + /// Enables or disables the track. Disabled tracks won't animate their nodes/properties. + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + /// Returns `true` if the track is enabled, `false` - otherwise. + pub fn is_enabled(&self) -> bool { + self.enabled + } + + /// Sets target of the track. + pub fn with_target(mut self, target: T) -> Self { + self.target = target; + self + } +} + /// Track is responsible in animating a property of a single scene node. The track consists up to 4 parametric curves /// that contains the actual property data. Parametric curves allows the engine to perform various interpolations between /// key values. #[derive(Debug, Reflect, Clone, PartialEq)] -pub struct Track { +pub struct Track { binding: ValueBinding, frames: TrackDataContainer, - enabled: bool, - target: T, id: Uuid, } -impl Visit for Track { +impl Visit for Track { fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { let mut region = visitor.enter_region(name)?; - self.target.visit("Node", &mut region)?; - self.enabled.visit("Enabled", &mut region)?; - let _ = self.binding.visit("Binding", &mut region); // Backward compatibility let _ = self.id.visit("Id", &mut region); // Backward compatibility let _ = self.frames.visit("Frames", &mut region); // Backward compatibility @@ -55,19 +93,17 @@ impl Visit for Track { } } -impl Default for Track { +impl Default for Track { fn default() -> Self { Self { binding: ValueBinding::Position, frames: TrackDataContainer::default(), - enabled: true, - target: Default::default(), id: Uuid::new_v4(), } } } -impl Track { +impl Track { /// Creates a new track that will animate a property in the given binding. The `container` must have enough parametric /// curves to be able to produces property values. pub fn new(container: TrackDataContainer, binding: ValueBinding) -> Self { @@ -105,12 +141,6 @@ impl Track { } } - /// Sets target of the track. - pub fn with_target(mut self, target: T) -> Self { - self.target = target; - self - } - /// Sets new track binding. See [`ValueBinding`] docs for more info. pub fn set_binding(&mut self, binding: ValueBinding) { self.binding = binding; @@ -121,16 +151,6 @@ impl Track { &self.binding } - /// Sets a handle of a node that will be animated. - pub fn set_target(&mut self, target: T) { - self.target = target; - } - - /// Returns a handle of a node that will be animated. - pub fn target(&self) -> T { - self.target - } - /// Returns a reference to the data container. pub fn data_container(&self) -> &TrackDataContainer { &self.frames @@ -154,16 +174,6 @@ impl Track { }) } - /// Enables or disables the track. Disabled tracks won't animate their nodes/properties. - pub fn set_enabled(&mut self, enabled: bool) { - self.enabled = enabled; - } - - /// Returns `true` if the track is enabled, `false` - otherwise. - pub fn is_enabled(&self) -> bool { - self.enabled - } - /// Returns length of the track in seconds. pub fn time_length(&self) -> f32 { self.frames.time_length() diff --git a/fyrox-impl/src/resource/fbx/mod.rs b/fyrox-impl/src/resource/fbx/mod.rs index 0a1ebd4f5..05980fdde 100644 --- a/fyrox-impl/src/resource/fbx/mod.rs +++ b/fyrox-impl/src/resource/fbx/mod.rs @@ -750,7 +750,6 @@ async fn convert_model( // Convert to engine format let mut translation_track = Track::new_position(); - translation_track.set_target(node_handle); if let Some(lcl_translation) = lcl_translation { fill_track( &mut translation_track, @@ -764,7 +763,6 @@ async fn convert_model( } let mut rotation_track = Track::new_rotation(); - rotation_track.set_target(node_handle); if let Some(lcl_rotation) = lcl_rotation { fill_track( &mut rotation_track, @@ -778,16 +776,15 @@ async fn convert_model( } let mut scale_track = Track::new_scale(); - scale_track.set_target(node_handle); if let Some(lcl_scale) = lcl_scale { fill_track(&mut scale_track, fbx_scene, lcl_scale, model.scale, |v| v); } else { add_vec3_key(&mut scale_track, model.scale); } - animation.add_track(translation_track); - animation.add_track(rotation_track); - animation.add_track(scale_track); + animation.add_track(node_handle, translation_track); + animation.add_track(node_handle, rotation_track); + animation.add_track(node_handle, scale_track); } animation.fit_length_to_content(); @@ -829,7 +826,7 @@ async fn convert( } // Do not create animation player if there's no animation content. - if !animation.tracks().is_empty() { + if !animation.tracks_data().data_ref().tracks().is_empty() { let mut animations_container = AnimationContainer::new(); animations_container.add(animation); AnimationPlayerBuilder::new(BaseBuilder::new().with_name("AnimationPlayer")) diff --git a/fyrox-impl/src/resource/gltf/animation.rs b/fyrox-impl/src/resource/gltf/animation.rs index 9f377b47f..946d928e9 100644 --- a/fyrox-impl/src/resource/gltf/animation.rs +++ b/fyrox-impl/src/resource/gltf/animation.rs @@ -185,15 +185,15 @@ impl ImportedTrack { false } } - fn into_track(self) -> Track> { + fn into_track(self) -> (Handle, Track) { let mut data = TrackDataContainer::new(self.target.kind()); for (i, curve) in self.curves.into_vec().into_iter().enumerate() { data.curves_mut()[i] = Curve::from(curve); } - let mut track = Track::new(data, self.target.value_binding()); - track.set_target(self.target.handle); - track.set_enabled(true); - track + ( + self.target.handle, + Track::new(data, self.target.value_binding()), + ) } } @@ -224,7 +224,8 @@ impl ImportedAnimation { result.set_name(self.name); result.set_time_slice(self.start..self.end); for t in self.tracks { - result.add_track(t.into_track()); + let (node, track) = t.into_track(); + result.add_track(node, track); } result } diff --git a/fyrox-impl/src/resource/model/mod.rs b/fyrox-impl/src/resource/model/mod.rs index 60fad6cae..2229cb69b 100644 --- a/fyrox-impl/src/resource/model/mod.rs +++ b/fyrox-impl/src/resource/model/mod.rs @@ -249,17 +249,30 @@ pub trait AnimationSource { // Remap animation track nodes from resource to instance. This is required // because we've made a plain copy and it has tracks with node handles mapped // to nodes of internal scene. - for (i, ref_track) in src_anim.tracks().iter().enumerate() { - let ref_node = &model_graph.node(ref_track.target()); - let track = &mut anim_copy.tracks_mut()[i]; + let src_anim_track_data = src_anim.tracks_data().state(); + let Some(src_anim_track_data) = src_anim_track_data.data_ref() else { + continue; + }; + + for ref_track in src_anim_track_data.tracks.iter() { + let Some(ref_track_binding) = + src_anim.track_bindings().get(&ref_track.id()) + else { + continue; + }; + + let ref_node = &model_graph.node(ref_track_binding.target); + let track_binding = anim_copy + .track_bindings_mut() + .get_mut(&ref_track.id()) + .unwrap(); // Find instantiated node that corresponds to node in resource match graph.find_by_name(root, ref_node.name()) { Some((instance_node, _)) => { - // One-to-one track mapping so there is [i] indexing. - track.set_target(instance_node); + track_binding.set_target(instance_node); } None => { - track.set_target(Default::default()); + track_binding.set_target(Default::default()); Log::writeln( MessageKind::Error, format!( diff --git a/fyrox-impl/src/scene/animation/mod.rs b/fyrox-impl/src/scene/animation/mod.rs index 179f05130..252366016 100644 --- a/fyrox-impl/src/scene/animation/mod.rs +++ b/fyrox-impl/src/scene/animation/mod.rs @@ -48,7 +48,7 @@ pub mod spritesheet; /// Scene specific animation. pub type Animation = crate::generic_animation::Animation>; /// Scene specific animation track. -pub type Track = crate::generic_animation::track::Track>; +pub type Track = crate::generic_animation::track::Track; /// Scene specific animation container. pub type AnimationContainer = crate::generic_animation::AnimationContainer>; /// Scene specific animation pose. diff --git a/fyrox-resource/src/lib.rs b/fyrox-resource/src/lib.rs index fd84d5c21..20662be79 100644 --- a/fyrox-resource/src/lib.rs +++ b/fyrox-resource/src/lib.rs @@ -139,6 +139,14 @@ where None } } + + pub fn data_ref(&self) -> Option<&T> { + if let ResourceState::Ok(ref data) = self.guard.state { + ResourceData::as_any(&**data).downcast_ref::() + } else { + None + } + } } /// A resource of particular data type. It is a typed wrapper around [`UntypedResource`] which diff --git a/fyrox-ui/src/animation.rs b/fyrox-ui/src/animation.rs index 0a82bebcd..035e6b2d6 100644 --- a/fyrox-ui/src/animation.rs +++ b/fyrox-ui/src/animation.rs @@ -65,7 +65,7 @@ impl AnimationPlayerMessage { /// UI-specific animation. pub type Animation = crate::generic_animation::Animation>; /// UI-specific animation track. -pub type Track = crate::generic_animation::track::Track>; +pub type Track = crate::generic_animation::track::Track; /// UI-specific animation container. pub type AnimationContainer = crate::generic_animation::AnimationContainer>; /// UI-specific animation pose.