diff --git a/editor/src/absm/mod.rs b/editor/src/absm/mod.rs index 9a4d50126..e96a2bede 100644 --- a/editor/src/absm/mod.rs +++ b/editor/src/absm/mod.rs @@ -571,8 +571,8 @@ impl AbsmEditor { let mut animation_targets = FxHashSet::default(); for animation in animations.iter_mut() { - for track in animation.tracks() { - animation_targets.insert(track.target()); + for track_binding in animation.track_bindings().values() { + animation_targets.insert(track_binding.target()); } } diff --git a/editor/src/absm/toolbar.rs b/editor/src/absm/toolbar.rs index 97104c214..a0201e051 100644 --- a/editor/src/absm/toolbar.rs +++ b/editor/src/absm/toolbar.rs @@ -255,8 +255,8 @@ impl Toolbar { animation_container_ref(graph, selection.absm_node_handle) { for animation in animations.iter() { - for track in animation.tracks() { - unique_nodes.insert(track.target()); + for track_binding in animation.track_bindings().values() { + unique_nodes.insert(track_binding.target()); } } } diff --git a/editor/src/animation/command/mod.rs b/editor/src/animation/command/mod.rs index 14027a61b..3fc9f3f9a 100644 --- a/editor/src/animation/command/mod.rs +++ b/editor/src/animation/command/mod.rs @@ -38,6 +38,7 @@ use crate::{ scene::{commands::GameSceneContext, Selection}, ui_scene::commands::UiSceneContext, }; +use fyrox::generic_animation::track::TrackBinding; use std::{ fmt::Debug, ops::{IndexMut, Range}, @@ -73,19 +74,22 @@ pub fn fetch_animations_container( pub struct AddTrackCommand { animation_player: Handle, animation: Handle>>, - track: Option>>, + track: Option, + binding: TrackBinding>, } impl AddTrackCommand { pub fn new( animation_player: Handle, animation: Handle>>, - track: Track>, + track: Track, + binding: TrackBinding>, ) -> Self { Self { animation_player, animation, track: Some(track), + binding, } } } @@ -97,12 +101,16 @@ impl CommandTrait for AddTrackCommand { fn execute(&mut self, context: &mut dyn CommandContext) { fetch_animations_container(self.animation_player, context)[self.animation] - .add_track(self.track.take().unwrap()); + .add_track_with_binding(self.binding.clone(), self.track.take().unwrap()); } fn revert(&mut self, context: &mut dyn CommandContext) { - self.track = - fetch_animations_container(self.animation_player, context)[self.animation].pop_track(); + let (binding, track) = fetch_animations_container(self.animation_player, context) + [self.animation] + .pop_track_with_binding() + .unwrap(); + self.binding = binding; + self.track = Some(track); } } @@ -111,7 +119,8 @@ pub struct RemoveTrackCommand { animation_player: Handle, animation: Handle>>, id: Uuid, - track: Option<(usize, Track>)>, + #[allow(clippy::type_complexity)] + track: Option<(usize, (TrackBinding>, Track))>, } impl RemoveTrackCommand { @@ -138,17 +147,22 @@ impl CommandTrait for RemoveTrackCommand { let animation = &mut fetch_animations_container(self.animation_player, context)[self.animation]; let index = animation + .tracks_data() + .state() + .data() + .unwrap() .tracks_mut() .iter() .position(|t| t.id() == self.id) .unwrap(); - self.track = Some((index, animation.remove_track(index))); + self.track = Some((index, animation.remove_track_with_binding(index).unwrap())); } fn revert(&mut self, context: &mut dyn CommandContext) { - let (index, track) = self.track.take().unwrap(); - fetch_animations_container(self.animation_player, context)[self.animation] - .insert_track(index, track); + let (index, (binding, track)) = self.track.take().unwrap(); + let animation = + &mut fetch_animations_container(self.animation_player, context)[self.animation]; + animation.insert_track_with_binding(index, binding, track); } } @@ -161,9 +175,13 @@ pub struct ReplaceTrackCurveCommand { impl ReplaceTrackCurveCommand { fn swap(&mut self, context: &mut dyn CommandContext) { - for track in - fetch_animations_container(self.animation_player, context)[self.animation].tracks_mut() - { + let animation = + &mut fetch_animations_container(self.animation_player, context)[self.animation]; + + let mut tracks_data_state = animation.tracks_data().state(); + let tracks_data = tracks_data_state.data().unwrap(); + + for track in tracks_data.tracks_mut() { for curve in track.data_container_mut().curves_mut() { if curve.id() == self.curve.id() { std::mem::swap(&mut self.curve, curve); @@ -601,9 +619,8 @@ pub struct SetTrackEnabledCommand { impl SetTrackEnabledCommand { fn swap(&mut self, context: &mut dyn CommandContext) { let track = fetch_animation(self.animation_player_handle, self.animation_handle, context) - .tracks_mut() - .iter_mut() - .find(|t| t.id() == self.track) + .track_bindings_mut() + .get_mut(&self.track) .unwrap(); let old = track.is_enabled(); @@ -637,9 +654,8 @@ pub struct SetTrackTargetCommand { impl SetTrackTargetCommand { fn swap(&mut self, context: &mut dyn CommandContext) { let track = fetch_animation(self.animation_player_handle, self.animation_handle, context) - .tracks_mut() - .iter_mut() - .find(|t| t.id() == self.track) + .track_bindings_mut() + .get_mut(&self.track) .unwrap(); let old = track.target(); @@ -663,28 +679,33 @@ impl CommandTrait for SetTrackTargetCommand { } #[derive(Debug)] -pub struct SetTrackBindingCommand { +pub struct SetTrackValueBindingCommand { pub animation_player_handle: Handle, pub animation_handle: Handle>>, pub track: Uuid, pub binding: ValueBinding, } -impl SetTrackBindingCommand { +impl SetTrackValueBindingCommand { fn swap(&mut self, context: &mut dyn CommandContext) { - let track = fetch_animation(self.animation_player_handle, self.animation_handle, context) + let animation = + fetch_animation(self.animation_player_handle, self.animation_handle, context); + let mut tracks_data_state = animation.tracks_data().state(); + let tracks_data = tracks_data_state.data().unwrap(); + + let track = tracks_data .tracks_mut() .iter_mut() .find(|t| t.id() == self.track) .unwrap(); - let old = track.binding().clone(); - track.set_binding(self.binding.clone()); + let old = track.value_binding().clone(); + track.set_value_binding(self.binding.clone()); self.binding = old; } } -impl CommandTrait for SetTrackBindingCommand { +impl CommandTrait for SetTrackValueBindingCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { "Set Track Binding".to_string() } diff --git a/editor/src/animation/mod.rs b/editor/src/animation/mod.rs index 1f24f2e6f..91a33b8ed 100644 --- a/editor/src/animation/mod.rs +++ b/editor/src/animation/mod.rs @@ -468,9 +468,9 @@ impl AnimationEditor { animation.rewind(); let animation_targets = animation - .tracks() - .iter() - .map(|t| t.target()) + .track_bindings() + .values() + .map(|binding| binding.target()) .collect::>(); self.enter_preview_mode( @@ -689,6 +689,11 @@ impl AnimationEditor { self.track_list .sync_to_model(animation, graph, &selection, ui); + let animation_tracks_data_state = animation.tracks_data().state(); + let Some(animation_tracks_data) = animation_tracks_data_state.data_ref() else { + return; + }; + send_sync_message( ui, CurveEditorMessage::hightlight_zones( @@ -729,7 +734,8 @@ impl AnimationEditor { SelectedEntity::Track(track_id) => { // If a track is selected, show all its curves at once. This way it will // be easier to edit complex values, such as Vector2/3/4. - if let Some(track) = animation + + if let Some(track) = animation_tracks_data .tracks() .iter() .find(|track| &track.id() == track_id) @@ -748,7 +754,7 @@ impl AnimationEditor { } SelectedEntity::Curve(curve_id) => { if let Some((index, selected_curve)) = - animation.tracks().iter().find_map(|t| { + animation_tracks_data.tracks().iter().find_map(|t| { t.data_container().curves_ref().iter().enumerate().find_map( |(i, c)| { if &c.id() == curve_id { @@ -772,7 +778,7 @@ impl AnimationEditor { } } let mut background_curves = Vec::::new(); - for track in animation.tracks() { + for track in animation_tracks_data.tracks() { for curve in track.data_container().curves_ref() { if !selected_curves.iter().any(|(_, c)| c.id == curve.id) { background_curves.push(curve.clone()); diff --git a/editor/src/animation/track.rs b/editor/src/animation/track.rs index 229a78356..fb3b911fb 100644 --- a/editor/src/animation/track.rs +++ b/editor/src/animation/track.rs @@ -24,8 +24,8 @@ use crate::{ animation::{ animation_container_ref, command::{ - AddTrackCommand, RemoveTrackCommand, SetTrackBindingCommand, SetTrackEnabledCommand, - SetTrackTargetCommand, + AddTrackCommand, RemoveTrackCommand, SetTrackEnabledCommand, SetTrackTargetCommand, + SetTrackValueBindingCommand, }, selection::{AnimationSelection, SelectedEntity}, }, @@ -91,6 +91,7 @@ use crate::{ }, send_sync_message, utils, }; +use fyrox::generic_animation::track::TrackBinding; use fyrox::renderer::framework::DrawParameters; use fyrox::scene::mesh::buffer::{TriangleBuffer, VertexBuffer}; use std::{ @@ -884,21 +885,24 @@ impl TrackList { sender.do_command(AddTrackCommand::new( selection.animation_player, selection.animation, - Track::new_position().with_target(self.selected_node.into()), + Track::new_position(), + TrackBinding::new(self.selected_node.into()), )); } PropertyBindingMode::Rotation => { sender.do_command(AddTrackCommand::new( selection.animation_player, selection.animation, - Track::new_rotation().with_target(self.selected_node.into()), + Track::new_rotation(), + TrackBinding::new(self.selected_node.into()), )); } PropertyBindingMode::Scale => { sender.do_command(AddTrackCommand::new( selection.animation_player, selection.animation, - Track::new_scale().with_target(self.selected_node.into()), + Track::new_scale(), + TrackBinding::new(self.selected_node.into()), )); } } @@ -936,7 +940,7 @@ impl TrackList { let types = type_id_to_supported_type(property_type); if let Some((track_value_kind, actual_value_type)) = types { - let mut track = Track::new( + let track = Track::new( TrackDataContainer::new(track_value_kind), ValueBinding::Property { name: property_path.path.clone().into(), @@ -944,12 +948,11 @@ impl TrackList { }, ); - track.set_target(self.selected_node.into()); - sender.do_command(AddTrackCommand::new( selection.animation_player, selection.animation, track, + TrackBinding::new(self.selected_node.into()), )); } } @@ -1048,13 +1051,17 @@ impl TrackList { .iter() .filter_map(|e| match e { SelectedEntity::Track(track_id) => { - let index = animation + let state = animation.tracks_data().state(); + let tracks_data = state.data_ref()?; + let binding = animation.track_bindings().get(track_id)?; + + let index = tracks_data .tracks() .iter() .position(|t| t.id() == *track_id) .unwrap(); - let mut track = animation.tracks()[index].clone(); + let mut track = tracks_data.tracks()[index].clone(); track.set_id(Uuid::new_v4()); @@ -1062,6 +1069,7 @@ impl TrackList { selection.animation_player, selection.animation, track, + TrackBinding::new(binding.target), ))) } _ => None, @@ -1078,11 +1086,7 @@ impl TrackList { .node(message.destination()) .query_component::() { - if animation - .tracks() - .iter() - .any(|t| t.id() == track_view_ref.id) - { + if animation.track_bindings().contains_key(&track_view_ref.id) { sender.do_command(SetTrackEnabledCommand { animation_player_handle: selection.animation_player, animation_handle: selection.animation, @@ -1170,13 +1174,9 @@ impl TrackList { return; }; - if let Some(track) = animation - .tracks() - .iter() - .find(|t| t.id() == first_selected_track) - { + if let Some(binding) = animation.track_bindings().get(&first_selected_track) { self.context_menu.property_rebinding_selector = - Self::open_property_selector(graph, track.target(), ui); + Self::open_property_selector(graph, binding.target(), ui); } } @@ -1195,15 +1195,11 @@ impl TrackList { return; }; - let Some(track) = animation - .tracks() - .iter() - .find(|t| t.id() == first_selected_track) - else { + let Some(binding) = animation.track_bindings().get(&first_selected_track) else { return; }; - let Some(node) = graph.try_get(track.target()) else { + let Some(node) = graph.try_get(binding.target()) else { Log::err("Invalid node handle!"); return; }; @@ -1216,7 +1212,7 @@ impl TrackList { let types = type_id_to_supported_type(property_type); if let Some((_, actual_value_type)) = types { - sender.do_command(SetTrackBindingCommand { + sender.do_command(SetTrackValueBindingCommand { animation_player_handle: selection.animation_player, animation_handle: selection.animation, track: first_selected_track, @@ -1254,17 +1250,22 @@ impl TrackList { G: SceneGraph, N: SceneGraphNode, { + let state = animation.tracks_data().state(); + let Some(tracks_data) = state.data_ref() else { + return; + }; + if Handle::>>::from(self.selected_animation) != selection.animation { self.clear(ui); self.selected_animation = selection.animation.into(); } - match animation.tracks().len().cmp(&self.track_views.len()) { + match tracks_data.tracks().len().cmp(&self.track_views.len()) { Ordering::Less => { for track_view in self.track_views.clone().values() { let track_view_ref = ui.node(*track_view); let track_view_data = track_view_ref.query_component::().unwrap(); - if animation + if tracks_data .tracks() .iter() .all(|t| t.id() != track_view_data.id) @@ -1323,48 +1324,55 @@ impl TrackList { // Nothing to do. } Ordering::Greater => { - for model_track in animation.tracks().iter() { + for model_track in tracks_data.tracks().iter() { + let Some(model_track_binding) = + animation.track_bindings().get(&model_track.id()) + else { + continue; + }; + if self .track_views .values() .map(|v| ui.node(*v)) .all(|v| v.query_component::().unwrap().id != model_track.id()) { - let parent_group = match self.group_views.entry(model_track.target().into()) - { - Entry::Occupied(entry) => *entry.get(), - Entry::Vacant(entry) => { - let ctx = &mut ui.build_ctx(); - let group = TreeBuilder::new(WidgetBuilder::new()) - .with_content( - TextBuilder::new( - WidgetBuilder::new() - .with_vertical_alignment(VerticalAlignment::Center), + let parent_group = + match self.group_views.entry(model_track_binding.target().into()) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + let ctx = &mut ui.build_ctx(); + let group = TreeBuilder::new(WidgetBuilder::new()) + .with_content( + TextBuilder::new( + WidgetBuilder::new().with_vertical_alignment( + VerticalAlignment::Center, + ), + ) + .with_text(format!( + "{} ({}:{})", + graph + .try_get(model_track_binding.target()) + .map(|n| n.name()) + .unwrap_or_default(), + model_track_binding.target().index(), + model_track_binding.target().generation() + )) + .build(ctx), ) - .with_text(format!( - "{} ({}:{})", - graph - .try_get(model_track.target()) - .map(|n| n.name()) - .unwrap_or_default(), - model_track.target().index(), - model_track.target().generation() - )) - .build(ctx), - ) - .build(ctx); - send_sync_message( - ui, - TreeRootMessage::add_item( - self.tree_root, - MessageDirection::ToWidget, - group, - ), - ); + .build(ctx); + send_sync_message( + ui, + TreeRootMessage::add_item( + self.tree_root, + MessageDirection::ToWidget, + group, + ), + ); - *entry.insert(group) - } - }; + *entry.insert(group) + } + }; let ctx = &mut ui.build_ctx(); @@ -1453,10 +1461,10 @@ impl TrackList { ) .with_items(curves), ) - .with_track_enabled(model_track.is_enabled()) + .with_track_enabled(model_track_binding.is_enabled()) .with_id(model_track.id()) - .with_target(model_track.target().into()) - .with_name(format!("{}", model_track.binding())) + .with_target(model_track_binding.target().into()) + .with_name(format!("{}", model_track.value_binding())) .build(ctx); send_sync_message( @@ -1513,23 +1521,30 @@ impl TrackList { ), ); - for track_model in animation.tracks() { - if let Some(track_view) = self.track_views.get(&track_model.id()) { + for model_track in tracks_data.tracks() { + let Some(model_track_binding) = animation.track_bindings().get(&model_track.id()) + else { + continue; + }; + + if let Some(track_view) = self.track_views.get(&model_track.id()) { let track_view_ref = ui.node(*track_view).query_component::().unwrap(); - if track_view_ref.track_enabled != track_model.is_enabled() { + if track_view_ref.track_enabled != model_track_binding.is_enabled() { send_sync_message( ui, TrackViewMessage::track_enabled( *track_view, MessageDirection::ToWidget, - track_model.is_enabled(), + model_track_binding.is_enabled(), ), ); } let mut validation_result = Ok(()); - if let Some(target) = graph.try_get(track_model.target()) { - if let Some(parent_group) = self.group_views.get(&track_model.target().into()) { + if let Some(target) = graph.try_get(model_track_binding.target()) { + if let Some(parent_group) = + self.group_views.get(&model_track_binding.target().into()) + { send_sync_message( ui, TextMessage::text( @@ -1541,8 +1556,8 @@ impl TrackList { format!( "{} ({}:{})", target.name(), - track_model.target().index(), - track_model.target().generation() + model_track_binding.target().index(), + model_track_binding.target().generation() ), ), ); @@ -1553,11 +1568,12 @@ impl TrackList { TrackViewMessage::track_name( *track_view, MessageDirection::ToWidget, - format!("{}", track_model.binding()), + format!("{}", model_track.value_binding()), ), ); - if let ValueBinding::Property { name, value_type } = track_model.binding() { + if let ValueBinding::Property { name, value_type } = model_track.value_binding() + { target.resolve_path(name, &mut |result| match result { Ok(value) => { let mut property_type = TypeId::of::(); 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..b022b4f89 100644 --- a/fyrox-animation/src/lib.rs +++ b/fyrox-animation/src/lib.rs @@ -28,29 +28,34 @@ 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::container::TrackDataContainer; +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 +65,90 @@ pub mod spritesheet; pub mod track; pub mod value; +/// A container for animation tracks. Multiple animations can share the same container to reduce +/// memory consumption. It could be extremely useful in case of many instances of a little amount +/// of kinds of animated models. +#[derive(Default, Debug, Reflect, Clone, PartialEq, TypeUuidProvider)] +#[type_uuid(id = "044d9f7c-5c6c-4b29-8de9-d0d975a48256")] +pub struct AnimationTracksData { + /// Tracks of the animation. See [`Track`] docs for more info. + pub tracks: Vec, +} + +impl AnimationTracksData { + /// 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 AnimationTracksData { + fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { + self.tracks.visit(name, visitor) + } +} + +impl ResourceData for AnimationTracksData { + 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 + } +} + +/// A resource that holds animation tracks. This resource can be shared across multiple animations. +pub type AnimationTracksDataResource = Resource; + /// # Overview /// /// Animation allows you to change properties of arbitrary entities at runtime using a set of key frames. Animation @@ -137,18 +226,18 @@ pub mod value; /// a guide **only** if you need to create procedural animations: /// /// ```rust -/// use fyrox_animation::{ -/// container::{TrackDataContainer, TrackValueKind}, -/// track::Track, -/// value::ValueBinding, -/// Animation, -/// core::{ -/// math::curve::{Curve, CurveKey, CurveKeyKind}, -/// pool::Handle, -/// }, -/// }; -/// use fyrox_core::pool::ErasedHandle; -/// +/// # use fyrox_animation::{ +/// # container::{TrackDataContainer, TrackValueKind}, +/// # track::Track, +/// # value::ValueBinding, +/// # Animation, +/// # core::{ +/// # math::curve::{Curve, CurveKey, CurveKeyKind}, +/// # pool::Handle, +/// # }, +/// # }; +/// # use fyrox_animation::track::TrackBinding; +/// # use fyrox_core::pool::ErasedHandle; /// fn create_animation(target: ErasedHandle) -> Animation { /// let mut frames_container = TrackDataContainer::new(TrackValueKind::Vector3); /// @@ -159,13 +248,12 @@ pub mod value; /// CurveKey::new(1.0, 3.0, CurveKeyKind::Linear), /// ]); /// -/// // Create a track that will animated the node using the curve above. +/// // Create a track that will animate the node using the curve above. /// let mut track = Track::new(frames_container, ValueBinding::Position); -/// track.set_target(target); /// /// // Finally create an animation and set its time slice and turn it on. /// let mut animation = Animation::default(); -/// animation.add_track(track); +/// animation.add_track_with_binding(TrackBinding::new(target), track); /// animation.set_time_slice(0.0..1.0); /// animation.set_enabled(true); /// @@ -184,36 +272,89 @@ pub mod value; /// The code above creates a simple animation that moves a node along X axis in various ways. The usage of the animation /// is only for the sake of completeness of the example. In the real games you need to add the animation to an animation /// player scene node and it will do the job for you. -#[derive(Debug, Reflect, Visit, PartialEq)] +#[derive(Debug, Reflect, PartialEq)] pub struct Animation { - #[visit(optional)] name: ImmutableString, - tracks: Vec>, + tracks_data: AnimationTracksDataResource, + track_bindings: FxHashMap>, time_position: f32, - #[visit(optional)] time_slice: Range, speed: f32, looped: bool, enabled: bool, signals: Vec, - - #[visit(optional)] root_motion_settings: Option>, + max_event_capacity: usize, #[reflect(hidden)] - #[visit(skip)] root_motion: Option, - // Non-serialized #[reflect(hidden)] - #[visit(skip)] pose: AnimationPose, // Non-serialized #[reflect(hidden)] - #[visit(skip)] events: VecDeque, - #[visit(optional)] - max_event_capacity: usize, +} + +#[derive(Visit, Default)] +struct OldTrack { + binding: ValueBinding, + frames: TrackDataContainer, + enabled: bool, + node: T, + id: Uuid, +} + +impl Visit for Animation { + fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { + let mut region = visitor.enter_region(name)?; + + // Backward compatibility. + let mut old_tracks = Vec::>::new(); + if region.is_reading() { + if old_tracks.visit("Tracks", &mut region).is_ok() { + let mut tracks_data = AnimationTracksData::default(); + for old_track in old_tracks { + self.track_bindings.insert( + old_track.id, + TrackBinding { + enabled: old_track.enabled, + target: old_track.node, + }, + ); + tracks_data.tracks.push(Track { + binding: old_track.binding, + frames: old_track.frames, + id: old_track.id, + }); + } + self.tracks_data = + AnimationTracksDataResource::new_ok(ResourceKind::Embedded, tracks_data); + } else { + self.tracks_data.visit("TracksData", &mut region)?; + self.track_bindings.visit("TrackBindings", &mut region)?; + } + } else { + self.tracks_data.visit("TracksData", &mut region)?; + self.track_bindings.visit("TrackBindings", &mut region)?; + } + + let _ = self.name.visit("Name", &mut region); + self.time_position.visit("TimePosition", &mut region)?; + let _ = self.time_slice.visit("TimeSlice", &mut region); + self.speed.visit("Speed", &mut region)?; + self.looped.visit("Looped", &mut region)?; + self.enabled.visit("Enabled", &mut region)?; + self.signals.visit("Signals", &mut region)?; + let _ = self + .max_event_capacity + .visit("MaxEventCapacity", &mut region); + let _ = self + .root_motion_settings + .visit("RootMotionSettings", &mut region); + + Ok(()) + } } impl TypeUuidProvider for Animation { @@ -224,7 +365,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 +425,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 +437,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 +463,16 @@ 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) + /// Sets a new source of data for animation tracks. Keep in mind, that all existing track bindings + /// stored in the animation could become invalid, if the new resource does not have tracks with + /// the same ids that the bindings has. + pub fn set_tracks_data(&mut self, resource: AnimationTracksDataResource) { + self.tracks_data = resource; } - /// 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 the current animation tracks resource. + pub fn tracks_data(&self) -> &AnimationTracksDataResource { + &self.tracks_data } /// Calculates new length of the animation based on the content of its tracks. It looks for the most "right" @@ -347,19 +480,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,10 +568,16 @@ 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) + .find(|track| track.value_binding() == &ValueBinding::Position) .and_then(|track| track.fetch(time)) .and_then(|value| { if let TrackValue::Vector3(position) = value.value { @@ -451,13 +589,10 @@ 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) + .find(|track| track.value_binding() == &ValueBinding::Rotation) .and_then(|track| track.fetch(time)) .and_then(|value| { if let TrackValue::UnitQuaternion(rotation) = value.value { @@ -499,9 +634,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 +671,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 +697,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 +719,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 +821,65 @@ impl Animation { self.enabled } - /// Returns a mutable reference to the track container. - pub fn tracks_mut(&mut self) -> &mut [Track] { - &mut self.tracks + /// Adds a new track to the animation track data container and binds it with the specified target. + /// Keep in mind, that this method will modify potentially shared track data container, which might + /// affect other animations using it. + pub fn add_track_with_binding(&mut self, binding: TrackBinding, 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, binding); + } + + /// Removes last track from the current tracks data resource and the respective binding to it + /// from the animation. This method will fail if the resource is not loaded, or if there's no + /// tracks in it. It will also fail if there's no respective binding to the track in the + /// animation. + /// + /// Keep in mind, that this method modifies the tracks data resource, which might be used by + /// some other animation. + pub fn pop_track_with_binding(&mut self) -> Option<(TrackBinding, Track)> { + let mut state = self.tracks_data.state(); + let tracks_data = state.data()?; + let track = tracks_data.tracks.pop()?; + let binding = self.track_bindings.remove(&track.id())?; + Some((binding, track)) + } + + /// Removes the specified track from the current tracks data resource and the respective binding + /// to it from the animation. This method will fail if the resource is not loaded, or if there's + /// no tracks in it. It will also fail if there's no respective binding to the track in the + /// animation. + /// + /// Keep in mind, that this method modifies the tracks data resource, which might be used by + /// some other animation. + pub fn remove_track_with_binding(&mut self, index: usize) -> Option<(TrackBinding, Track)> { + let mut state = self.tracks_data.state(); + let tracks_data = state.data()?; + let track = tracks_data.tracks.remove(index); + let binding = self.track_bindings.remove(&track.id())?; + Some((binding, track)) + } + + /// Inserts a new track in the tracks data resource and creates a new binding to it. + /// + /// Keep in mind, that this method modifies the tracks data resource, which might be used by + /// some other animation. + pub fn insert_track_with_binding( + &mut self, + index: usize, + binding: TrackBinding, + track: Track, + ) { + let mut state = self.tracks_data.state(); + let Some(tracks_data) = state.data() else { + return; + }; + assert!(self.track_bindings.insert(track.id(), binding).is_none()); + tracks_data.tracks.insert(index, track); } /// Adds a new animation signal to the animation. See [`AnimationSignal`] docs for more info and examples. @@ -722,36 +913,25 @@ 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) + /// Returns a reference to the current set of track bindings used by the animation. The returned + /// hash map contains `(track_id -> binding)` pairs. + 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) + /// Returns a reference to the current set of track bindings used by the animation. The returned + /// hash map contains `(track_id -> binding)` pairs. + 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 +961,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 +994,7 @@ impl Default for Animation { fn default() -> Self { Self { name: Default::default(), - tracks: Vec::new(), + tracks_data: Resource::new_ok(ResourceKind::Embedded, AnimationTracksData::default()), speed: 1.0, time_position: 0.0, enabled: true, @@ -817,6 +1006,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..c92aa3012 100644 --- a/fyrox-animation/src/track.rs +++ b/fyrox-animation/src/track.rs @@ -28,25 +28,77 @@ use crate::{ }; use std::fmt::Debug; +/// Track binding contains a handle to a target object that will be animated by an animation track. +/// Additionally, the binding could be disabled to temporarily prevent animation from affecting the +/// target. +#[derive(Debug, Visit, Reflect, Clone, PartialEq)] +pub struct TrackBinding { + /// The binding could be disabled to temporarily prevent animation from affecting the target. + pub enabled: bool, + /// A target bound to a track. The actual track id is stored as a key in hash map of bindings in + /// the animation. + pub target: T, +} + +impl Default for TrackBinding { + fn default() -> Self { + Self { + enabled: true, + target: Default::default(), + } + } +} + +impl TrackBinding { + /// Creates a new enabled track binding. + pub fn new(target: T) -> Self { + Self { + enabled: true, + target, + } + } + + /// 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 { - binding: ValueBinding, - frames: TrackDataContainer, - enabled: bool, - target: T, - id: Uuid, +pub struct Track { + pub(super) binding: ValueBinding, + pub(super) frames: TrackDataContainer, + pub(super) 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 +107,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,32 +155,16 @@ 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) { + pub fn set_value_binding(&mut self, binding: ValueBinding) { self.binding = binding; } /// Returns current track binding. - pub fn binding(&self) -> &ValueBinding { + pub fn value_binding(&self) -> &ValueBinding { &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 +188,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-animation/src/value.rs b/fyrox-animation/src/value.rs index 9a675b4c9..8092d36f2 100644 --- a/fyrox-animation/src/value.rs +++ b/fyrox-animation/src/value.rs @@ -300,9 +300,10 @@ impl TrackValue { /// cases for the most used properties and a generic one for arbitrary properties. Arbitrary properties are set using /// reflection system, while the special cases handles bindings to standard properties (such as position, scaling, or /// rotation) for optimization. Reflection is quite slow to be used as the universal property setting mechanism. -#[derive(Clone, Visit, Reflect, Debug, PartialEq, Eq)] +#[derive(Default, Clone, Visit, Reflect, Debug, PartialEq, Eq)] pub enum ValueBinding { /// A binding to position of a scene node. + #[default] Position, /// A binding to scale of a scene node. Scale, diff --git a/fyrox-graph/src/lib.rs b/fyrox-graph/src/lib.rs index d6e19dea3..6df13c87e 100644 --- a/fyrox-graph/src/lib.rs +++ b/fyrox-graph/src/lib.rs @@ -254,6 +254,21 @@ where return; } + entity.as_hash_map_mut(&mut |hash_map| { + if let Some(hash_map) = hash_map { + for i in 0..hash_map.reflect_len() { + if let Some(item) = hash_map.reflect_get_nth_value_mut(i) { + self.remap_handles_internal(item, node_name, ignored_types); + } + } + mapped = true; + } + }); + + if mapped { + return; + } + // Continue remapping recursively for every compound field. entity.fields_mut(&mut |fields| { for field in fields { @@ -366,6 +381,28 @@ where return; } + entity.as_hash_map_mut(&mut |result| { + if let Some(hash_map) = result { + for i in 0..hash_map.reflect_len() { + if let Some(item) = hash_map.reflect_get_nth_value_mut(i) { + self.remap_inheritable_handles_internal( + item, + node_name, + // Propagate mapping flag - it means that we're inside inheritable variable. In this + // case we will map handles. + do_map, + ignored_types, + ); + } + } + mapped = true; + } + }); + + if mapped { + return; + } + // Continue remapping recursively for every compound field. entity.fields_mut(&mut |fields| { for field in fields { diff --git a/fyrox-impl/src/resource/fbx/mod.rs b/fyrox-impl/src/resource/fbx/mod.rs index 0a1ebd4f5..2995cc07e 100644 --- a/fyrox-impl/src/resource/fbx/mod.rs +++ b/fyrox-impl/src/resource/fbx/mod.rs @@ -78,6 +78,7 @@ use crate::{ utils::{self, raw_mesh::RawMeshBuilder}, }; use fxhash::{FxHashMap, FxHashSet}; +use fyrox_animation::track::TrackBinding; use fyrox_resource::io::ResourceIo; use fyrox_resource::untyped::ResourceKind; use std::{cmp::Ordering, path::Path}; @@ -750,7 +751,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 +764,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 +777,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_with_binding(TrackBinding::new(node_handle), translation_track); + animation.add_track_with_binding(TrackBinding::new(node_handle), rotation_track); + animation.add_track_with_binding(TrackBinding::new(node_handle), scale_track); } animation.fit_length_to_content(); @@ -829,7 +827,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..83d1a7414 100644 --- a/fyrox-impl/src/resource/gltf/animation.rs +++ b/fyrox-impl/src/resource/gltf/animation.rs @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use super::iter::*; +use super::simplify::*; use crate::core::algebra::{Quaternion, Unit, UnitQuaternion, Vector3}; use crate::core::log::Log; use crate::core::math::curve::{Curve, CurveKey, CurveKeyKind}; @@ -30,15 +32,13 @@ use crate::scene::animation::Animation; use crate::scene::graph::Graph; use crate::scene::mesh::Mesh; use crate::scene::node::Node; +use fyrox_animation::track::TrackBinding; use fyrox_graph::BaseSceneGraph; use gltf::animation::util::ReadOutputs; use gltf::animation::Channel; use gltf::animation::{Interpolation, Property}; use gltf::Buffer; -use super::iter::*; -use super::simplify::*; - type Result = std::result::Result; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -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_with_binding(TrackBinding::new(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..b6d317ee9 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. @@ -190,6 +190,7 @@ impl BoundValueCollectionExt for BoundValueCollection { /// next code snippet is for you. /// /// ```rust +/// # use fyrox_animation::track::TrackBinding; /// # use fyrox_impl::{ /// # core::{ /// # math::curve::{Curve, CurveKey, CurveKeyKind}, @@ -211,13 +212,12 @@ impl BoundValueCollectionExt for BoundValueCollection { /// CurveKey::new(0.6, 0.0, CurveKeyKind::Linear), /// ]); /// -/// // Create a track that will animated the node using the curve above. +/// // Create a track that will animate the node using the curve above. /// let mut track = Track::new(frames_container, ValueBinding::Position); -/// track.set_target(animated_node); -/// + /// // Finally create an animation and set its time slice and turn it on. /// let mut animation = Animation::default(); -/// animation.add_track(track); +/// animation.add_track_with_binding(TrackBinding::new(animated_node),track); /// animation.set_time_slice(0.0..0.6); /// animation.set_enabled(true); /// animation 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.