From b332a13b4e543adc95c5f8b9b99a963c7d111d1a Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 17 Aug 2016 22:48:55 +0900 Subject: [PATCH 1/8] Don't restart looped animations on repeated calls --- apps/openmw/mwmechanics/character.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c925302599..711feb4a61 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2009,6 +2009,9 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int } else { + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && isAnimPlaying(mAnimQueue.front().mGroup)) + return true; + count = std::max(count, 1); AnimationQueueEntry entry; From bf9dc45b2bafb39994789239c22c61a980b00b3e Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 18 Aug 2016 01:08:54 +0900 Subject: [PATCH 2/8] Emulate vanilla animation loops more closely --- apps/openmw/mwmechanics/character.cpp | 27 +++++++-- apps/openmw/mwrender/animation.cpp | 87 ++++++++++++++++----------- apps/openmw/mwrender/animation.hpp | 9 ++- 3 files changed, 83 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 711feb4a61..5310dfdbbc 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1533,6 +1533,9 @@ void CharacterController::update(float duration) updateMagicEffects(); + if(mAnimQueue.size() > 1 && (mAnimation->getLoopingEnabled(mAnimQueue.front().mGroup) == true)) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, false); + if(!cls.isActor()) { if(mAnimQueue.size() > 1) @@ -2009,8 +2012,26 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int } else { - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && isAnimPlaying(mAnimQueue.front().mGroup)) - return true; + // If the given animation is a looped animation, is already playing + // and has not yet reached its Loop Stop key, make it the only animation + // in the queue, and retain the loop count from the animation that was + // already playing. This emulates observed behavior from the original + // engine and allows banners to animate correctly. + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && + mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop start") >= 0) + { + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); + + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); + + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, true); + mAnimQueue.resize(1); + return true; + } + } count = std::max(count, 1); @@ -2035,8 +2056,6 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int } else if(mode == 0) { - if (!mAnimQueue.empty()) - mAnimation->stopLooping(mAnimQueue.front().mGroup); mAnimQueue.resize(1); mAnimQueue.push_back(entry); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e5614f3f8f..03370e7f83 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1023,50 +1023,54 @@ namespace MWRender { float targetTime; - if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) - goto handle_loop; - - targetTime = state.getTime() + timepassed; - if(textkey == textkeys.end() || textkey->first > targetTime) + if (state.getTime() < state.mLoopStopTime || state.mLoopCount == 0) { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement); - state.setTime(std::min(targetTime, state.mStopTime)); - } - else - { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement); - state.setTime(textkey->first); - } + targetTime = state.getTime() + timepassed; + if(textkey == textkeys.end() || textkey->first > targetTime) + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), targetTime, movement); + state.setTime(std::min(targetTime, state.mStopTime)); + } + else + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), textkey->first, movement); + state.setTime(textkey->first); + } - state.mPlaying = (state.getTime() < state.mStopTime); - timepassed = targetTime - state.getTime(); + state.mPlaying = (state.getTime() < state.mStopTime); + timepassed = targetTime - state.getTime(); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) - { - handleTextKey(state, stateiter->first, textkey, textkeys); - ++textkey; - } - - if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) - { - handle_loop: - state.mLoopCount--; - state.setTime(state.mLoopStartTime); - state.mPlaying = true; - - textkey = textkeys.lower_bound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } - - if(state.getTime() >= state.mLoopStopTime) - break; } + if(state.getTime() >= state.mLoopStopTime) + { + if (!state.mLoopingEnabled) + state.mLoopCount = 0; + else if (state.mLoopCount > 0) + { + state.mLoopCount--; + state.setTime(state.mLoopStartTime); + state.mPlaying = true; + + textkey = textkeys.lower_bound(state.getTime()); + while(textkey != textkeys.end() && textkey->first <= state.getTime()) + { + handleTextKey(state, stateiter->first, textkey, textkeys); + ++textkey; + } + + if(state.getTime() >= state.mLoopStopTime) + break; + } + } + if(timepassed <= 0.0f) break; } @@ -1095,6 +1099,21 @@ namespace MWRender return movement; } + void Animation::setLoopingEnabled(const std::string &groupname, bool enabled) + { + AnimStateMap::iterator state(mStates.find(groupname)); + if(state != mStates.end()) + state->second.mLoopingEnabled = enabled; + } + + bool Animation::getLoopingEnabled(const std::string &groupname) const + { + AnimStateMap::const_iterator state(mStates.find(groupname)); + if(state != mStates.end()) + return state->second.mLoopingEnabled; + return false; + } + void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) { osg::ref_ptr previousStateset; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a837a26aeb..2eb857cbd0 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -183,6 +183,7 @@ protected: float mSpeedMult; bool mPlaying; + bool mLoopingEnabled; size_t mLoopCount; AnimPriority mPriority; @@ -190,8 +191,8 @@ protected: bool mAutoDisable; AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), - mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), - mPriority(0), mBlendMask(0), mAutoDisable(true) + mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopingEnabled(true), + mLoopCount(0), mPriority(0), mBlendMask(0), mAutoDisable(true) { } ~AnimState(); @@ -432,6 +433,10 @@ public: virtual osg::Vec3f runAnimation(float duration); + void setLoopingEnabled(const std::string &groupname, bool enabled); + + bool getLoopingEnabled(const std::string &groupname) const; + /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); From 6450c9be27a1a16683c658e93ecc47c80a4a7b7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Aug 2016 22:57:56 +0200 Subject: [PATCH 3/8] Simplify condition --- apps/openmw/mwmechanics/character.cpp | 4 ++-- apps/openmw/mwrender/animation.cpp | 8 -------- apps/openmw/mwrender/animation.hpp | 2 -- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5310dfdbbc..be321ce28a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1533,8 +1533,8 @@ void CharacterController::update(float duration) updateMagicEffects(); - if(mAnimQueue.size() > 1 && (mAnimation->getLoopingEnabled(mAnimQueue.front().mGroup) == true)) - mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, false); + if(!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); if(!cls.isActor()) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 03370e7f83..a0b68fd6cf 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1106,14 +1106,6 @@ namespace MWRender state->second.mLoopingEnabled = enabled; } - bool Animation::getLoopingEnabled(const std::string &groupname) const - { - AnimStateMap::const_iterator state(mStates.find(groupname)); - if(state != mStates.end()) - return state->second.mLoopingEnabled; - return false; - } - void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) { osg::ref_ptr previousStateset; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2eb857cbd0..4eeb5d9434 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -435,8 +435,6 @@ public: void setLoopingEnabled(const std::string &groupname, bool enabled); - bool getLoopingEnabled(const std::string &groupname) const; - /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); From 0c9882956afe8ad20b9673af0adf2e1627fd571b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Aug 2016 22:58:24 +0200 Subject: [PATCH 4/8] Add AnimState::shouldLoop() --- apps/openmw/mwrender/animation.cpp | 32 ++++++++++++------------------ apps/openmw/mwrender/animation.hpp | 5 +++++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a0b68fd6cf..29407bc2de 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1023,7 +1023,7 @@ namespace MWRender { float targetTime; - if (state.getTime() < state.mLoopStopTime || state.mLoopCount == 0) + if (!state.shouldLoop()) { targetTime = state.getTime() + timepassed; if(textkey == textkeys.end() || textkey->first > targetTime) @@ -1048,27 +1048,21 @@ namespace MWRender ++textkey; } } - - if(state.getTime() >= state.mLoopStopTime) + if(state.shouldLoop()) { - if (!state.mLoopingEnabled) - state.mLoopCount = 0; - else if (state.mLoopCount > 0) + state.mLoopCount--; + state.setTime(state.mLoopStartTime); + state.mPlaying = true; + + textkey = textkeys.lower_bound(state.getTime()); + while(textkey != textkeys.end() && textkey->first <= state.getTime()) { - state.mLoopCount--; - state.setTime(state.mLoopStartTime); - state.mPlaying = true; - - textkey = textkeys.lower_bound(state.getTime()); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) - { - handleTextKey(state, stateiter->first, textkey, textkeys); - ++textkey; - } - - if(state.getTime() >= state.mLoopStopTime) - break; + handleTextKey(state, stateiter->first, textkey, textkeys); + ++textkey; } + + if(state.getTime() >= state.mLoopStopTime) + break; } if(timepassed <= 0.0f) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4eeb5d9434..749890ae5b 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -205,6 +205,11 @@ protected: { *mTime = time; } + + bool shouldLoop() const + { + return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; + } }; typedef std::map AnimStateMap; AnimStateMap mStates; From 719e884b7c01d9416a957d17eb48183e87185424 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Aug 2016 23:02:57 +0200 Subject: [PATCH 5/8] Remove duplicate code --- apps/openmw/mwmechanics/character.cpp | 48 +++++++++++---------------- apps/openmw/mwmechanics/character.hpp | 2 ++ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index be321ce28a..873d3ce7e9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1524,6 +1524,23 @@ bool CharacterController::updateWeaponState() return forcestateupdate; } +void CharacterController::updateAnimQueue() +{ + if(mAnimQueue.size() > 1) + { + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, + MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + } + } +} + void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -1537,21 +1554,7 @@ void CharacterController::update(float duration) mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); if(!cls.isActor()) - { - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } - } + updateAnimQueue(); else if(!cls.getCreatureStats(mPtr).isDead()) { bool onground = world->isOnGround(mPtr); @@ -1819,19 +1822,8 @@ void CharacterController::update(float duration) { idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); } - else if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } + else + updateAnimQueue(); if (!mSkipAnim) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d5dc5fe284..4661d6983a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -212,6 +212,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateCreatureState(); void updateIdleStormState(bool inwater); + void updateAnimQueue(); + void updateHeadTracking(float duration); void updateMagicEffects(); From b0dc625b185b8d21a4808806790d36eafcec5eb2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Aug 2016 23:03:26 +0200 Subject: [PATCH 6/8] Run setLoopingEnabled after the anim queue is updated --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 873d3ce7e9..319e3074f4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1539,6 +1539,9 @@ void CharacterController::updateAnimQueue() 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } } + + if(!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } void CharacterController::update(float duration) @@ -1550,9 +1553,6 @@ void CharacterController::update(float duration) updateMagicEffects(); - if(!mAnimQueue.empty()) - mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); - if(!cls.isActor()) updateAnimQueue(); else if(!cls.getCreatureStats(mPtr).isDead()) From 0fd810707ef21f8c7a904dd2dd6e1030491b9185 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Aug 2016 23:04:05 +0200 Subject: [PATCH 7/8] Remove unused stopLooping() --- apps/openmw/mwrender/animation.cpp | 10 ---------- apps/openmw/mwrender/animation.hpp | 4 ---- 2 files changed, 14 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 29407bc2de..32fa5e6009 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -873,16 +873,6 @@ namespace MWRender addControllers(); } - void Animation::stopLooping(const std::string& groupname) - { - AnimStateMap::iterator stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - stateiter->second.mLoopCount = 0; - return; - } - } - void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) { AnimStateMap::iterator state(mStates.find(groupname)); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 749890ae5b..d0191d172c 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -395,10 +395,6 @@ public: float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback=false); - /** If the given animation group is currently playing, set its remaining loop count to '0'. - */ - void stopLooping(const std::string& groupName); - /** Adjust the speed multiplier of an already playing animation. */ void adjustSpeedMult (const std::string& groupname, float speedmult); From 9b0e5d6b5900a3cdef293951f5ea2e24a0ac4924 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 23 Aug 2016 19:50:56 +0900 Subject: [PATCH 8/8] Loop mid-animation idles when loading a save game --- apps/openmw/mwmechanics/character.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 319e3074f4..637c243007 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1989,9 +1989,10 @@ void CharacterController::unpersistAnimationState() mCurrentIdle.clear(); mIdleState = CharState_SpecialIdle; + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); mAnimation->play(anim.mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", complete, anim.mLoopCount); + "start", "stop", complete, anim.mLoopCount, loopfallback); } }