initial attempt to decouple animation tracks data into a shared resource

- significantly reduces memory constumption when cloning animations, since it does not need to clone the tracks anymore.
- animation resource can be shared across multiple animations using the same tracks
- significantly speeds up instantiation of animation player scene node
- the editor is still broken and requires proper fixes
- no backward compatibility yet
This commit is contained in:
Dmitry Stepanov
2024-11-07 22:24:20 +03:00
parent f1f10a445a
commit 3521280d76
9 changed files with 240 additions and 134 deletions
+1
View File
@@ -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"
+152 -76
View File
@@ -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<Track>,
}
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<Track> {
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<F>(&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 {
<AnimationData as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
// TODO
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
}
pub type AnimationDataResource = Resource<AnimationData>;
/// # 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<T: EntityId> {
#[visit(optional)]
name: ImmutableString,
tracks: Vec<Track<T>>,
tracks_data: AnimationDataResource,
track_bindings: FxHashMap<Uuid, TrackBinding<T>>,
time_position: f32,
#[visit(optional)]
time_slice: Range<f32>,
@@ -224,7 +308,7 @@ impl<T: EntityId> TypeUuidProvider for Animation<T> {
/// 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<T: EntityId> Clone for Animation<T> {
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<T: EntityId> Clone for Animation<T> {
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<T: EntityId> Animation<T> {
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<T>) {
self.tracks.push(track);
}
/// Removes a track at given index.
pub fn remove_track(&mut self, index: usize) -> Track<T> {
self.tracks.remove(index)
}
/// Inserts a track at given index.
pub fn insert_track(&mut self, index: usize, track: Track<T>) {
self.tracks.insert(index, track)
}
/// Removes last track from the list of tracks of the animation.
pub fn pop_track(&mut self) -> Option<Track<T>> {
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<T: EntityId> Animation<T> {
/// 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<T>] {
&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<T: EntityId> Animation<T> {
}
fn update_root_motion(&mut self, prev_time_position: f32) {
fn fetch_position_at_time<T: EntityId>(tracks: &[Track<T>], time: f32) -> Vector3<f32> {
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<f32> {
tracks
.iter()
.find(|track| track.binding() == &ValueBinding::Position)
@@ -451,10 +524,7 @@ impl<T: EntityId> Animation<T> {
.unwrap_or_default()
}
fn fetch_rotation_at_time<T: EntityId>(
tracks: &[Track<T>],
time: f32,
) -> UnitQuaternion<f32> {
fn fetch_rotation_at_time(tracks: &[Track], time: f32) -> UnitQuaternion<f32> {
tracks
.iter()
.find(|track| track.binding() == &ValueBinding::Rotation)
@@ -499,9 +569,9 @@ impl<T: EntityId> Animation<T> {
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<T: EntityId> Animation<T> {
// 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<T: EntityId> Animation<T> {
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<T: EntityId> Animation<T> {
// 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<T: EntityId> Animation<T> {
self.enabled
}
/// Returns a mutable reference to the track container.
pub fn tracks_mut(&mut self) -> &mut [Track<T>] {
&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<T: EntityId> Animation<T> {
&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<F>(&mut self, filter: F)
where
F: FnMut(&Track<T>) -> 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<Item = &Track<T>> {
self.tracks
.iter()
.filter(move |track| track.target() == handle)
pub fn track_bindings(&self) -> &FxHashMap<Uuid, TrackBinding<T>> {
&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<Item = &mut Track<T>> {
self.tracks
.iter_mut()
.filter(move |track| track.target() == handle)
pub fn track_bindings_mut(&mut self) -> &mut FxHashMap<Uuid, TrackBinding<T>> {
&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<T: EntityId> Animation<T> {
/// 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<T: EntityId> Default for Animation<T> {
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<T: EntityId> Default for Animation<T> {
time_slice: Default::default(),
root_motion: None,
max_event_capacity: 32,
track_bindings: Default::default(),
}
}
}
+47 -37
View File
@@ -28,25 +28,63 @@ use crate::{
};
use std::fmt::Debug;
#[derive(Debug, Visit, Reflect, Clone, PartialEq)]
pub struct TrackBinding<T: EntityId> {
pub enabled: bool,
pub target: T,
}
impl<T: EntityId> Default for TrackBinding<T> {
fn default() -> Self {
Self {
enabled: true,
target: Default::default(),
}
}
}
impl<T: EntityId> TrackBinding<T> {
/// 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<T: EntityId> {
pub struct Track {
binding: ValueBinding,
frames: TrackDataContainer,
enabled: bool,
target: T,
id: Uuid,
}
impl<T: EntityId> Visit for Track<T> {
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<T: EntityId> Visit for Track<T> {
}
}
impl<T: EntityId> Default for Track<T> {
impl Default for Track {
fn default() -> Self {
Self {
binding: ValueBinding::Position,
frames: TrackDataContainer::default(),
enabled: true,
target: Default::default(),
id: Uuid::new_v4(),
}
}
}
impl<T: EntityId> Track<T> {
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<T: EntityId> Track<T> {
}
}
/// 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<T: EntityId> Track<T> {
&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<T: EntityId> Track<T> {
})
}
/// 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()
+4 -7
View File
@@ -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"))
+7 -6
View File
@@ -185,15 +185,15 @@ impl ImportedTrack {
false
}
}
fn into_track(self) -> Track<Handle<Node>> {
fn into_track(self) -> (Handle<Node>, 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
}
+19 -6
View File
@@ -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!(
+1 -1
View File
@@ -48,7 +48,7 @@ pub mod spritesheet;
/// Scene specific animation.
pub type Animation = crate::generic_animation::Animation<Handle<Node>>;
/// Scene specific animation track.
pub type Track = crate::generic_animation::track::Track<Handle<Node>>;
pub type Track = crate::generic_animation::track::Track;
/// Scene specific animation container.
pub type AnimationContainer = crate::generic_animation::AnimationContainer<Handle<Node>>;
/// Scene specific animation pose.
+8
View File
@@ -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::<T>()
} else {
None
}
}
}
/// A resource of particular data type. It is a typed wrapper around [`UntypedResource`] which
+1 -1
View File
@@ -65,7 +65,7 @@ impl AnimationPlayerMessage {
/// UI-specific animation.
pub type Animation = crate::generic_animation::Animation<Handle<UiNode>>;
/// UI-specific animation track.
pub type Track = crate::generic_animation::track::Track<Handle<UiNode>>;
pub type Track = crate::generic_animation::track::Track;
/// UI-specific animation container.
pub type AnimationContainer = crate::generic_animation::AnimationContainer<Handle<UiNode>>;
/// UI-specific animation pose.