From 46836aeb3989efb761419abdae8fc8d61fa9c268 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Thu, 5 Feb 2026 13:42:33 +0300 Subject: [PATCH 1/2] Animator refactoring --- engine/includes/components/actor.h | 5 + engine/includes/components/animator.h | 22 ++- engine/includes/resources/animationclip.h | 8 +- .../resources/animationstatemachine.h | 4 +- engine/src/components/animator.cpp | 143 +++++++++--------- .../src/editor/converters/assimpconverter.cpp | 2 +- engine/src/resources/animationclip.cpp | 24 ++- .../src/resources/animationstatemachine.cpp | 2 + engine/tests/tst_animator.h | 14 +- 9 files changed, 126 insertions(+), 98 deletions(-) diff --git a/engine/includes/components/actor.h b/engine/includes/components/actor.h index 6bb37e74b..ee45e11f3 100644 --- a/engine/includes/components/actor.h +++ b/engine/includes/components/actor.h @@ -56,6 +56,11 @@ class ENGINE_EXPORT Actor : public Object { Component *componentInChild(const TString &type); + template + T *getComponentInChild() { + return static_cast(componentInChild(T::metaClass()->name())); + } + std::list componentsInChild(const TString &type) const; Component *addComponent(const TString &type); diff --git a/engine/includes/components/animator.h b/engine/includes/components/animator.h index 4041df5d8..2e0fa68c9 100644 --- a/engine/includes/components/animator.h +++ b/engine/includes/components/animator.h @@ -40,9 +40,9 @@ class ENGINE_EXPORT Animator : public NativeBehaviour { }; - class TargetProperties { + class TargetProperty { public: - TargetProperties() : + TargetProperty() : property(nullptr) { } @@ -83,10 +83,12 @@ class ENGINE_EXPORT Animator : public NativeBehaviour { void setClip(AnimationClip *clip, float position = -1.0f); - void rebind(); + void update() override; private: - void update() override; + TargetProperty *bindTrack(const AnimationTrack &track); + + void process(float dt); void checkNextState(); @@ -96,13 +98,11 @@ class ENGINE_EXPORT Animator : public NativeBehaviour { bool updatePosition(PlaybackState &playback, float position) const; - void process(float dt); - - void sampleVector4(float dt, TargetProperties &target); + void sampleVector4(float dt, TargetProperty &target); - void sampleQuaternion(float dt, TargetProperties &target); + void sampleQuaternion(float dt, TargetProperty &target); - void sampleString(float dt, TargetProperties &target); + void sampleString(float dt, TargetProperty &target); static void stateMachineUpdated(int state, void *ptr); @@ -111,14 +111,12 @@ class ENGINE_EXPORT Animator : public NativeBehaviour { std::unordered_map m_currentVariables; - std::unordered_map m_bindProperties; + std::unordered_map m_bindProperties; AnimationStateMachine *m_stateMachine; AnimationState *m_currentState; - AnimationClip *m_currentClip; - float m_transitionDuration; }; diff --git a/engine/includes/resources/animationclip.h b/engine/includes/resources/animationclip.h index 3f5600dcb..8cf6c398e 100644 --- a/engine/includes/resources/animationclip.h +++ b/engine/includes/resources/animationclip.h @@ -1,7 +1,7 @@ #ifndef ANIMATIONCLIP_H #define ANIMATIONCLIP_H -#include "resource.h" +#include #include @@ -82,7 +82,7 @@ class ENGINE_EXPORT AnimationTrack : public Motion { int m_duration; }; -typedef std::list AnimationTrackList; +typedef std::vector AnimationTracks; class ENGINE_EXPORT AnimationClip : public Resource { A_OBJECT(AnimationClip, Resource, Resources) @@ -98,13 +98,13 @@ class ENGINE_EXPORT AnimationClip : public Resource { int addAnimationTrack(const AnimationTrack &track); void removeAnimationTrack(int index); - AnimationTrackList &tracks(); + AnimationTracks &tracks(); void loadUserData(const VariantMap &data) override; VariantMap saveUserData() const override; protected: - AnimationTrackList m_tracks; + AnimationTracks m_tracks; }; diff --git a/engine/includes/resources/animationstatemachine.h b/engine/includes/resources/animationstatemachine.h index 2eac9dc35..8b82993af 100644 --- a/engine/includes/resources/animationstatemachine.h +++ b/engine/includes/resources/animationstatemachine.h @@ -86,11 +86,11 @@ class ENGINE_EXPORT AnimationStateMachine : public Resource { const VariableMap &variables() const; + void loadUserData(const VariantMap &data) override; + static void registerSuper(ObjectSystem *system); private: - void loadUserData(const VariantMap &data) override; - AnimationTransitionCondition loadCondition(const VariantList &data) const; private: diff --git a/engine/src/components/animator.cpp b/engine/src/components/animator.cpp index 58f8abea1..e2a60eda6 100644 --- a/engine/src/components/animator.cpp +++ b/engine/src/components/animator.cpp @@ -30,7 +30,6 @@ enum TransformFlags { Animator::Animator() : m_stateMachine(nullptr), m_currentState(nullptr), - m_currentClip(nullptr), m_transitionDuration(0.0f) { } @@ -57,7 +56,7 @@ void Animator::process(float dt) { PROFILE_FUNCTION(); for(auto &it : m_bindProperties) { - TargetProperties &target = it.second; + TargetProperty &target = it.second; switch(target.defaultValue.type()) { case MetaType::QUATERNION: sampleQuaternion(dt, target); break; @@ -71,7 +70,7 @@ void Animator::process(float dt) { } } -void Animator::sampleVector4(float dt, TargetProperties &target) { +void Animator::sampleVector4(float dt, TargetProperty &target) { Vector4 vec4; float factor = 0.0f; @@ -122,7 +121,7 @@ void Animator::sampleVector4(float dt, TargetProperties &target) { } } -void Animator::sampleQuaternion(float dt, TargetProperties &target) { +void Animator::sampleQuaternion(float dt, TargetProperty &target) { Quaternion quat; float factor = 0.0f; @@ -163,7 +162,7 @@ void Animator::sampleQuaternion(float dt, TargetProperties &target) { } } -void Animator::sampleString(float dt, TargetProperties &target) { +void Animator::sampleString(float dt, TargetProperty &target) { TString str; float factor = 0.0f; @@ -274,7 +273,23 @@ void Animator::crossFadeHash(int hash, float duration) { m_currentState = newState; m_transitionDuration = duration; - setClip(m_currentState->m_clip); + if(m_transitionDuration == 0.0f) { + for(auto &it : m_bindProperties) { + it.second.playbacks.clear(); + } + } + + if(m_currentState->m_clip) { + AnimationTracks &tracks = m_currentState->m_clip->tracks(); + for(int i = 0; i < tracks.size(); i++) { + AnimationTrack &track = tracks[i]; + TargetProperty *target = bindTrack(track); + if(target) { + float weight = (m_transitionDuration != 0.0f) ? 0.0f : 1.0f; + target->playbacks.push_back({ &track, m_currentState, 0.0f, weight }); + } + } + } } } } @@ -292,74 +307,68 @@ void Animator::setClip(AnimationClip *clip, float position) { } } - if(clip == nullptr) { - return; - } - - m_currentClip = clip; - - rebind(); + if(clip) { + AnimationTracks &tracks = clip->tracks(); + for(int i = 0; i < tracks.size(); i++) { + AnimationTrack &track = tracks[i]; + TargetProperty *target = bindTrack(track); + if(target) { + float weight = (m_transitionDuration != 0.0f) ? 0.0f : 1.0f; + target->playbacks.push_back({ &track, nullptr, 0.0f, weight }); + } + } - if(position >= 0.0f) { - process(MIN(position, 1.0f) * m_currentClip->duration()); + if(position >= 0.0f) { + process(MIN(position, 1.0f) * clip->duration()); + } } } /*! - Rebinds all animated properties with Animator. + \internal */ -void Animator::rebind() { - PROFILE_FUNCTION(); - - if(m_currentClip) { - static const std::map tranformFlags = { - { "position", TransformFlags::Position }, - { "rotation", TransformFlags::Rotation }, - { "scale", TransformFlags::Scale }, - { "quaternion", TransformFlags::Quat } - }; - - Actor *actor = Animator::actor(); - for(auto &it : m_currentClip->tracks()) { - TargetProperties *target = nullptr; - - auto bind = m_bindProperties.find(it.hash()); - if(bind != m_bindProperties.end()) { - target = &(bind->second); - } else { - Object *object = actor->find(it.path()); - if(object) { - const MetaObject *meta = object->metaObject(); - int32_t index = meta->indexOfProperty(it.property().data()); - if(index > -1) { - TargetProperties data; - data.property = meta->property(index); - data.defaultValue = data.property.read(object); - data.object = object; - Transform *t = dynamic_cast(object); - if(t) { - auto transformIt = tranformFlags.find(it.property()); - if(transformIt != tranformFlags.end()) { - data.flag = transformIt->second; - } - } - - m_bindProperties[it.hash()] = data; - target = &m_bindProperties[it.hash()]; +Animator::TargetProperty *Animator::bindTrack(const AnimationTrack &track) { + static const std::map tranformFlags = { + { "position", TransformFlags::Position }, + { "rotation", TransformFlags::Rotation }, + { "scale", TransformFlags::Scale }, + { "quaternion", TransformFlags::Quat } + }; + + TargetProperty *target = nullptr; + + auto bind = m_bindProperties.find(track.hash()); + if(bind != m_bindProperties.end()) { + target = &(bind->second); + } else { + Object *object = Animator::actor()->find(track.path()); + if(object) { + const MetaObject *meta = object->metaObject(); + int32_t index = meta->indexOfProperty(track.property().data()); + if(index > -1) { + TargetProperty data; + data.property = meta->property(index); + data.defaultValue = data.property.read(object); + data.object = object; + Transform *t = dynamic_cast(object); + if(t) { + auto transformIt = tranformFlags.find(track.property()); + if(transformIt != tranformFlags.end()) { + data.flag = transformIt->second; } } -#ifdef SHARED_DEFINE - else { - aDebug() << "Can't resolve animation path:" << it.path(); - } -#endif - } - if(target) { - float weight = (m_transitionDuration != 0.0f) ? 0.0f : 1.0f; - target->playbacks.push_back({ &it, m_currentState, 0.0f, weight }); + m_bindProperties[track.hash()] = data; + target = &m_bindProperties[track.hash()]; } } +#ifdef SHARED_DEFINE + else { + aDebug() << "Can't resolve animation path:" << track.path(); + } +#endif } + + return target; } /*! Sets the new boolean \a value for the parameter with the \a name. @@ -444,7 +453,6 @@ void Animator::stateMachineUpdated(int state, void *ptr) { p->m_currentVariables.clear(); p->m_stateMachine = nullptr; p->m_currentState = nullptr; - p->m_currentClip = nullptr; } break; default: break; } @@ -492,7 +500,7 @@ void Animator::checkEndOfTransition() { bool endOfTransition = true; for(auto &it : m_bindProperties) { - const TargetProperties &target = it.second; + const TargetProperty &target = it.second; for(const auto &playback : target.playbacks) { if(playback.state != m_currentState) { endOfTransition = false; @@ -510,9 +518,8 @@ void Animator::checkEndOfTransition() { */ bool Animator::recalcTransitionWeights(PlaybackState &playback, float position, float &factor) const { if(m_transitionDuration > 0.0f) { - float offset = 1.0f - m_transitionDuration; - if(playback.state != m_currentState) { + float offset = 1.0f - m_transitionDuration; factor = MAX((position - offset) / m_transitionDuration, 0.0f); playback.weight = 1.0f - factor; if(playback.weight <= 0.0f) { @@ -533,7 +540,7 @@ bool Animator::updatePosition(PlaybackState &playback, float position) const { playback.currentPosition = position; if(playback.currentPosition >= 1.0f) { - if(playback.state->m_loop) { + if(playback.state && playback.state->m_loop) { while(playback.currentPosition >= 1.0f) { playback.currentPosition -= 1.0f; } diff --git a/engine/src/editor/converters/assimpconverter.cpp b/engine/src/editor/converters/assimpconverter.cpp index 5a12c2141..850900ef2 100644 --- a/engine/src/editor/converters/assimpconverter.cpp +++ b/engine/src/editor/converters/assimpconverter.cpp @@ -765,7 +765,7 @@ void AssimpConverter::importAnimation(const aiScene *scene, AssimpImportSettings } } - clip->tracks().sort(compare); + std::sort(clip->tracks().begin(), clip->tracks().end(), compare); Url dst(fbxSettings->absoluteDestination()); diff --git a/engine/src/resources/animationclip.cpp b/engine/src/resources/animationclip.cpp index a62aaaf54..abeed7f83 100644 --- a/engine/src/resources/animationclip.cpp +++ b/engine/src/resources/animationclip.cpp @@ -83,6 +83,8 @@ void AnimationTrack::setDuration(int duration) { Tries to fix animation curves in the animation track. Renormalizes existant keyframes and checks the duration. */ void AnimationTrack::fixCurves() { + PROFILE_FUNCTION(); + float scale = -1.0f; // Sort keys for(uint32_t j = 0; j < (m_curve.m_keys.size() - 1); j++) { @@ -114,6 +116,8 @@ void AnimationTrack::fixCurves() { Parameter normalized \a time is used to interpolate value between key frames. */ Vector4 AnimationTrack::valueVector4(float time) const { + PROFILE_FUNCTION(); + return m_curve.valueVector4(time); } /*! @@ -121,12 +125,16 @@ Vector4 AnimationTrack::valueVector4(float time) const { Parameter normalized \a time is used to interpolate value between key frames. */ Quaternion AnimationTrack::valueQuaternion(float time) const { + PROFILE_FUNCTION(); + return m_curve.valueQuaternion(time); } /*! Returns current value at normalized \a time position. */ TString AnimationTrack::valueString(float time) const { + PROFILE_FUNCTION(); + int32_t b = -1; for(uint32_t i = 0; i < m_frames.size(); i++) { if(time >= m_frames[i].m_position) { @@ -158,6 +166,8 @@ AnimationTrack::Frames &AnimationTrack::frames() { Serializes current track to Variant. */ Variant AnimationTrack::AnimationTrack::toVariant() const { + PROFILE_FUNCTION(); + VariantList track; track.push_back(path()); track.push_back(property()); @@ -199,6 +209,8 @@ Variant AnimationTrack::AnimationTrack::toVariant() const { Deserializes current track from \a variant. */ void AnimationTrack::fromVariant(const Variant &variant) { + PROFILE_FUNCTION(); + VariantList &trackData = *(reinterpret_cast(variant.data())); auto i = trackData.begin(); @@ -292,6 +304,8 @@ void AnimationClip::loadUserData(const VariantMap &data) { \internal */ VariantMap AnimationClip::saveUserData() const { + PROFILE_FUNCTION(); + VariantMap result; VariantList tracks; @@ -319,6 +333,8 @@ int AnimationClip::duration() const { Returns index of added track; */ int AnimationClip::addAnimationTrack(const AnimationTrack &track) { + PROFILE_FUNCTION(); + m_tracks.push_back(track); return m_tracks.size() - 1; @@ -327,12 +343,16 @@ int AnimationClip::addAnimationTrack(const AnimationTrack &track) { Removes animation track at givven \a index. */ void AnimationClip::removeAnimationTrack(int index) { + PROFILE_FUNCTION(); + auto it = std::next(m_tracks.begin(), index); if(it != m_tracks.end()) { m_tracks.erase(it); } } - -AnimationTrackList &AnimationClip::tracks() { +/*! + Returns all tracks associated with current animation clip. +*/ +AnimationTracks &AnimationClip::tracks() { return m_tracks; } diff --git a/engine/src/resources/animationstatemachine.cpp b/engine/src/resources/animationstatemachine.cpp index 76b6169b2..12f9d833a 100644 --- a/engine/src/resources/animationstatemachine.cpp +++ b/engine/src/resources/animationstatemachine.cpp @@ -106,6 +106,8 @@ void AnimationStateMachine::loadUserData(const VariantMap &data) { } block++; m_initialState = findState(Mathf::hashString((*block).toString())); + + switchState(ToBeUpdated); } } } diff --git a/engine/tests/tst_animator.h b/engine/tests/tst_animator.h index 01b0913b0..a4fc03920 100644 --- a/engine/tests/tst_animator.h +++ b/engine/tests/tst_animator.h @@ -104,15 +104,11 @@ class AnimatorTest : public ::testing::Test { return animator.m_currentState; } - AnimationClip *currentClip() const { - return animator.m_currentClip; - } - const std::unordered_map ¤tVariables() const { return animator.m_currentVariables; } - const Animator::TargetProperties &targetProperties(int index) const { + const Animator::TargetProperty &targetProperties(int index) const { auto it = std::next(animator.m_bindProperties.begin(), index); return it->second; } @@ -154,7 +150,7 @@ TEST_F(AnimatorTest, StateMachine) { ASSERT_EQ(&stateMachine, animator.stateMachine()); ASSERT_EQ(stateMachine.initialState(), currentState()); - ASSERT_EQ(&idleClip, currentClip()); + ASSERT_EQ(&idleClip, currentState()->m_clip); ASSERT_EQ(4, currentVariables().size()); } @@ -170,7 +166,7 @@ TEST_F(AnimatorTest, SwitchState) { animator.setState(gAttack); ASSERT_EQ(&attackState, currentState()); - ASSERT_EQ(&attackClip, currentClip()); + ASSERT_EQ(&attackClip, currentState()->m_clip); process(250); ASSERT_EQ(Vector3(0.0f, 0.75f, 0.0f), transform.position()); @@ -181,7 +177,7 @@ TEST_F(AnimatorTest, SwitchState) { checkNextState(); // Back to Idle after attack ASSERT_EQ(&idleState, currentState()); - ASSERT_EQ(&idleClip, currentClip()); + ASSERT_EQ(&idleClip, currentState()->m_clip); ASSERT_EQ(1, targetProperties(0).playbacks.size()); ASSERT_EQ(true, targetProperties(0).playbacks.front().state->m_loop); @@ -217,7 +213,7 @@ TEST_F(AnimatorTest, CrossFade) { checkNextState(); // Back to Idle after attack ASSERT_EQ(&idleState, currentState()); - ASSERT_EQ(&idleClip, currentClip()); + ASSERT_EQ(&idleClip, currentState()->m_clip); ASSERT_EQ(1, targetProperties(0).playbacks.size()); ASSERT_EQ(true, targetProperties(0).playbacks.front().state->m_loop); From 072b6ff60bf0d122185bc8ceb91184f370803a6d Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Thu, 5 Feb 2026 14:01:32 +0300 Subject: [PATCH 2/2] update --- engine/includes/resources/animationclip.h | 6 +++--- engine/src/components/animator.cpp | 8 ++++---- engine/src/editor/converters/assimpconverter.cpp | 2 +- engine/src/resources/animationclip.cpp | 2 +- modules/editor/timeline/editor/timelineedit.cpp | 4 +--- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/engine/includes/resources/animationclip.h b/engine/includes/resources/animationclip.h index 8cf6c398e..7ef29b23c 100644 --- a/engine/includes/resources/animationclip.h +++ b/engine/includes/resources/animationclip.h @@ -82,7 +82,7 @@ class ENGINE_EXPORT AnimationTrack : public Motion { int m_duration; }; -typedef std::vector AnimationTracks; +typedef std::list AnimationTrackList; class ENGINE_EXPORT AnimationClip : public Resource { A_OBJECT(AnimationClip, Resource, Resources) @@ -98,13 +98,13 @@ class ENGINE_EXPORT AnimationClip : public Resource { int addAnimationTrack(const AnimationTrack &track); void removeAnimationTrack(int index); - AnimationTracks &tracks(); + AnimationTrackList &tracks(); void loadUserData(const VariantMap &data) override; VariantMap saveUserData() const override; protected: - AnimationTracks m_tracks; + AnimationTrackList m_tracks; }; diff --git a/engine/src/components/animator.cpp b/engine/src/components/animator.cpp index e2a60eda6..e519c4377 100644 --- a/engine/src/components/animator.cpp +++ b/engine/src/components/animator.cpp @@ -280,9 +280,9 @@ void Animator::crossFadeHash(int hash, float duration) { } if(m_currentState->m_clip) { - AnimationTracks &tracks = m_currentState->m_clip->tracks(); + AnimationTrackList &tracks = m_currentState->m_clip->tracks(); for(int i = 0; i < tracks.size(); i++) { - AnimationTrack &track = tracks[i]; + AnimationTrack &track = *std::next(tracks.begin(), i); TargetProperty *target = bindTrack(track); if(target) { float weight = (m_transitionDuration != 0.0f) ? 0.0f : 1.0f; @@ -308,9 +308,9 @@ void Animator::setClip(AnimationClip *clip, float position) { } if(clip) { - AnimationTracks &tracks = clip->tracks(); + AnimationTrackList &tracks = clip->tracks(); for(int i = 0; i < tracks.size(); i++) { - AnimationTrack &track = tracks[i]; + AnimationTrack &track = *std::next(tracks.begin(), i); TargetProperty *target = bindTrack(track); if(target) { float weight = (m_transitionDuration != 0.0f) ? 0.0f : 1.0f; diff --git a/engine/src/editor/converters/assimpconverter.cpp b/engine/src/editor/converters/assimpconverter.cpp index 850900ef2..5a12c2141 100644 --- a/engine/src/editor/converters/assimpconverter.cpp +++ b/engine/src/editor/converters/assimpconverter.cpp @@ -765,7 +765,7 @@ void AssimpConverter::importAnimation(const aiScene *scene, AssimpImportSettings } } - std::sort(clip->tracks().begin(), clip->tracks().end(), compare); + clip->tracks().sort(compare); Url dst(fbxSettings->absoluteDestination()); diff --git a/engine/src/resources/animationclip.cpp b/engine/src/resources/animationclip.cpp index abeed7f83..9ed6845ec 100644 --- a/engine/src/resources/animationclip.cpp +++ b/engine/src/resources/animationclip.cpp @@ -353,6 +353,6 @@ void AnimationClip::removeAnimationTrack(int index) { /*! Returns all tracks associated with current animation clip. */ -AnimationTracks &AnimationClip::tracks() { +AnimationTrackList &AnimationClip::tracks() { return m_tracks; } diff --git a/modules/editor/timeline/editor/timelineedit.cpp b/modules/editor/timeline/editor/timelineedit.cpp index c8641b341..5493d9efa 100644 --- a/modules/editor/timeline/editor/timelineedit.cpp +++ b/modules/editor/timeline/editor/timelineedit.cpp @@ -222,9 +222,7 @@ void TimelineEdit::onPropertyUpdated(Object *object, const TString &property) { } void TimelineEdit::onRebind() { - if(m_controller) { - m_controller->rebind(); - } + setPosition(m_position); } void TimelineEdit::onSelectKey(int row, int index) {