From de6bbc356dc1222f7fe879a002bfe8af6c1488d4 Mon Sep 17 00:00:00 2001 From: badlogic Date: Tue, 11 Sep 2018 15:41:17 +0200 Subject: [PATCH] [cpp] Ported holdPrevious in AnimationState. See #1169. --- .../spine-cpp/include/spine/AnimationState.h | 42 +++-- .../spine-cpp/src/spine/AnimationState.cpp | 164 ++++++++++-------- 2 files changed, 122 insertions(+), 84 deletions(-) diff --git a/spine-cpp/spine-cpp/include/spine/AnimationState.h b/spine-cpp/spine-cpp/include/spine/AnimationState.h index 5eaa5e2fc..994b24944 100644 --- a/spine-cpp/spine-cpp/include/spine/AnimationState.h +++ b/spine-cpp/spine-cpp/include/spine/AnimationState.h @@ -76,6 +76,21 @@ namespace Spine { /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. bool getLoop(); void setLoop(bool inValue); + + /// + /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + /// of being mixed out. + /// + /// When mixing between animations that key the same property, if a lower track also keys that property then the value will + /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation + /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + /// keys the property, only when a higher track also keys the property. + /// + /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the + /// previous animation. + bool getHoldPrevious(); + void setHoldPrevious(bool inValue); /// /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing @@ -203,8 +218,13 @@ namespace Spine { /// /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no - /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list. + /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. TrackEntry* getMixingFrom(); + + /// + /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. + /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + TrackEntry* getMixingTo(); /// /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the @@ -224,25 +244,20 @@ namespace Spine { TrackEntry* _next; TrackEntry* _mixingFrom; + TrackEntry* _mixingTo; int _trackIndex; - bool _loop; + bool _loop, _holdPrevious; float _eventThreshold, _attachmentThreshold, _drawOrderThreshold; float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; MixBlend _mixBlend; - Vector _timelineData; - Vector _timelineDipMix; + Vector _timelineMode; + Vector _timelineHoldMix; Vector _timelinesRotation; OnAnimationEventFunc _onAnimationEventFunc; - /// Sets the timeline data. - /// @param to May be NULL. - TrackEntry* setTimelineData(TrackEntry* to, Vector& mixingToArray, Vector& propertyIDs); - - bool hasTimeline(int inId); - void reset(); }; @@ -384,7 +399,7 @@ namespace Spine { void* getRendererObject(); private: - static const int Subsequent, First, Dip, DipMix; + static const int Subsequent, First, Hold, HoldMix; AnimationStateData* _data; @@ -394,7 +409,6 @@ namespace Spine { EventQueue* _queue; Vector _propertyIDs; - Vector _mixingTo; bool _animationsChanged; void* _rendererObject; @@ -427,6 +441,10 @@ namespace Spine { void disposeNext(TrackEntry* entry); void animationsChanged(); + + void setTimelineModes(TrackEntry* entry); + + bool hasTimeline(TrackEntry* entry, int inId); }; } diff --git a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp index 9af748fbd..268f93d83 100644 --- a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp +++ b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp @@ -51,7 +51,7 @@ void dummyOnAnimationEventFunc(AnimationState *state, Spine::EventType type, Tra SP_UNUSED(event); } -TrackEntry::TrackEntry() : _animation(NULL), _next(NULL), _mixingFrom(NULL), _trackIndex(0), _loop(false), +TrackEntry::TrackEntry() : _animation(NULL), _next(NULL), _mixingFrom(NULL), _mixingTo(0), _trackIndex(0), _loop(false), _holdPrevious(false), _eventThreshold(0), _attachmentThreshold(0), _drawOrderThreshold(0), _animationStart(0), _animationEnd(0), _animationLast(0), _nextAnimationLast(0), _delay(0), _trackTime(0), _trackLast(0), _nextTrackLast(0), _trackEnd(0), _timeScale(1.0f), _alpha(0), _mixTime(0), @@ -67,6 +67,10 @@ bool TrackEntry::getLoop() { return _loop; } void TrackEntry::setLoop(bool inValue) { _loop = inValue; } +bool TrackEntry::getHoldPrevious() { return _holdPrevious; } + +void TrackEntry::setHoldPrevious(bool inValue) { _holdPrevious = inValue; } + float TrackEntry::getDelay() { return _delay; } void TrackEntry::setDelay(float inValue) { _delay = inValue; } @@ -143,6 +147,8 @@ void TrackEntry::setMixDuration(float inValue) { _mixDuration = inValue; } TrackEntry *TrackEntry::getMixingFrom() { return _mixingFrom; } +TrackEntry *TrackEntry::getMixingTo() { return _mixingTo; } + void TrackEntry::setMixBlend(MixBlend blend) { _mixBlend = blend; } MixBlend TrackEntry::getMixBlend() { return _mixBlend; } @@ -155,67 +161,14 @@ void TrackEntry::setOnAnimationEventFunc(OnAnimationEventFunc inValue) { _onAnimationEventFunc = inValue; } -TrackEntry *TrackEntry::setTimelineData(TrackEntry *to, Vector &mixingToArray, Vector &propertyIDs) { - if (to != NULL) mixingToArray.add(to); - TrackEntry *lastEntry = _mixingFrom != NULL ? _mixingFrom->setTimelineData(this, mixingToArray, propertyIDs) : this; - if (to != NULL) mixingToArray.removeAt(mixingToArray.size() - 1); - - size_t mixingToLast = mixingToArray.size() - 1; - Vector &timelines = _animation->_timelines; - size_t timelinesCount = timelines.size(); - _timelineData.setSize(timelinesCount, 0); - _timelineDipMix.setSize(timelinesCount, 0); - - // outer: - size_t i = 0; - continue_outer: - for (; i < timelinesCount; ++i) { - int id = timelines[i]->getPropertyId(); - if (propertyIDs.contains(id)) { - _timelineData[i] = AnimationState::Subsequent; - } else { - propertyIDs.add(id); - - if (to == NULL || !to->hasTimeline(id)) { - _timelineData[i] = AnimationState::First; - } else { - for (int ii = mixingToLast; ii >= 0; --ii) { - TrackEntry *entry = mixingToArray[ii]; - if (!entry->hasTimeline(id)) { - if (entry->_mixDuration > 0) { - _timelineData[i] = AnimationState::DipMix; - _timelineDipMix[i] = entry; - i++; - goto continue_outer; // continue outer; - } - break; - } - } - _timelineData[i] = AnimationState::Dip; - } - } - } - - return lastEntry; -} - -bool TrackEntry::hasTimeline(int inId) { - Vector &timelines = _animation->_timelines; - for (size_t i = 0, n = timelines.size(); i < n; ++i) { - if (timelines[i]->getPropertyId() == inId) { - return true; - } - } - return false; -} - void TrackEntry::reset() { _animation = NULL; _next = NULL; _mixingFrom = NULL; + _mixingTo = NULL; - _timelineData.clear(); - _timelineDipMix.clear(); + _timelineMode.clear(); + _timelineHoldMix.clear(); _timelinesRotation.clear(); _onAnimationEventFunc = dummyOnAnimationEventFunc; @@ -314,8 +267,8 @@ void EventQueue::drain() { const int AnimationState::Subsequent = 0; const int AnimationState::First = 1; -const int AnimationState::Dip = 2; -const int AnimationState::DipMix = 3; +const int AnimationState::Hold = 2; +const int AnimationState::HoldMix = 3; AnimationState::AnimationState(AnimationStateData *data) : _data(data), @@ -400,6 +353,7 @@ void AnimationState::update(float delta) { // End mixing from entries once all have completed. TrackEntry *from = current._mixingFrom; current._mixingFrom = NULL; + if (from != NULL) from->_mixingTo = NULL; while (from != NULL) { _queue->end(from); from = from->_mixingFrom; @@ -441,13 +395,13 @@ bool AnimationState::apply(Skeleton &skeleton) { float animationLast = current._animationLast, animationTime = current.getAnimationTime(); size_t timelineCount = current._animation->_timelines.size(); Vector &timelines = current._animation->_timelines; - if (mix == 1 || blend == MixBlend_Add) { + if (i == 0 && (mix == 1 || blend == MixBlend_Add)) { for (size_t ii = 0; ii < timelineCount; ++ii) { timelines[ii]->apply(skeleton, animationLast, animationTime, &_events, mix, blend, MixDirection_In); } } else { - Vector &timelineData = current._timelineData; + Vector &timelineMode = current._timelineMode; bool firstFrame = current._timelinesRotation.size() == 0; if (firstFrame) { @@ -459,7 +413,7 @@ bool AnimationState::apply(Skeleton &skeleton) { Timeline *timeline = timelines[ii]; assert(timeline); - MixBlend timelineBlend = timelineData[ii] == AnimationState::Subsequent ? blend : MixBlend_Setup; + MixBlend timelineBlend = timelineMode[ii] == AnimationState::Subsequent ? blend : MixBlend_Setup; RotateTimeline *rotateTimeline = NULL; if (timeline->getRTTI().isExactly(RotateTimeline::rtti)) { @@ -519,6 +473,7 @@ void AnimationState::clearTrack(size_t trackIndex) { _queue->end(from); entry->_mixingFrom = NULL; + entry->_mixingTo = NULL; entry = from; } @@ -764,6 +719,7 @@ bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) { // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). if (from->_totalAlpha == 0 || to->_mixDuration == 0) { to->_mixingFrom = from->_mixingFrom; + if (from->_mixingFrom != NULL) from->_mixingFrom->_mixingTo = to; to->_interruptAlpha = from->_interruptAlpha; _queue->end(from); } @@ -800,14 +756,14 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle float animationLast = from->_animationLast, animationTime = from->getAnimationTime(); Vector &timelines = from->_animation->_timelines; size_t timelineCount = timelines.size(); - float alphaDip = from->_alpha * to->_interruptAlpha, alphaMix = alphaDip * (1 - mix); + float alphaHold = from->_alpha * to->_interruptAlpha, alphaMix = alphaHold * (1 - mix); if (blend == MixBlend_Add) { for (size_t i = 0; i < timelineCount; i++) timelines[i]->apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection_Out); } else { - Vector &timelineData = from->_timelineData; - Vector &timelineDipMix = from->_timelineDipMix; + Vector &timelineMode = from->_timelineMode; + Vector &timelineHoldMix = from->_timelineHoldMix; bool firstFrame = from->_timelinesRotation.size() == 0; if (firstFrame) { @@ -821,7 +777,7 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle Timeline *timeline = timelines[i]; MixBlend timelineBlend; float alpha; - switch (timelineData[i]) { + switch (timelineMode[i]) { case AnimationState::Subsequent: if (!attachments && (timeline->getRTTI().isExactly(AttachmentTimeline::rtti))) continue; if (!drawOrder && (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti))) continue; @@ -832,14 +788,14 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle timelineBlend = MixBlend_Setup; alpha = alphaMix; break; - case AnimationState::Dip: + case AnimationState::Hold: timelineBlend = MixBlend_Setup; - alpha = alphaDip; + alpha = alphaHold; break; default: timelineBlend = MixBlend_Setup; - TrackEntry *dipMix = timelineDipMix[i]; - alpha = alphaDip * MathUtil::max(0.0f, 1.0f - dipMix->_mixTime / dipMix->_mixDuration); + TrackEntry *holdMix = timelineHoldMix[i]; + alpha = alphaHold * MathUtil::max(0.0f, 1.0f - holdMix->_mixTime / holdMix->_mixDuration); break; } from->_totalAlpha += alpha; @@ -912,6 +868,7 @@ void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrup } current->_mixingFrom = from; + from->_mixingTo = current; current->_mixTime = 0; // Store interrupted mix percentage. @@ -944,6 +901,7 @@ TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animatio entry._trackIndex = trackIndex; entry._animation = animation; entry._loop = loop; + entry._holdPrevious = 0; entry._eventThreshold = 0; entry._attachmentThreshold = 0; @@ -985,8 +943,70 @@ void AnimationState::animationsChanged() { for (size_t i = 0, n = _tracks.size(); i < n; ++i) { TrackEntry *entry = _tracks[i]; - if (entry != NULL && (i == 0 ||entry->_mixBlend != MixBlend_Add)) { - entry->setTimelineData(NULL, _mixingTo, _propertyIDs); + + while (entry->_mixingFrom != NULL) + entry = entry->_mixingFrom; + + do { + if (entry->_mixingTo == NULL || entry->_mixBlend != MixBlend_Add) setTimelineModes(entry); + entry = entry->_mixingTo; + } while (entry != NULL); + } +} + +void AnimationState::setTimelineModes(TrackEntry *entry) { + TrackEntry* to = entry->_mixingTo; + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + timelineMode.setSize(timelinesCount, 0); + Vector &timelineHoldMix = entry->_timelineHoldMix; + timelineHoldMix.setSize(timelinesCount, 0); + + if (to != NULL && to->_holdPrevious) { + for (size_t i = 0; i < timelinesCount; i++) { + int id = timelines[i]->getPropertyId(); + if (!_propertyIDs.contains(id)) _propertyIDs.add(id); + timelineMode[i] = AnimationState::Hold; + } + return; + } + + // outer: + size_t i = 0; + continue_outer: + for (; i < timelinesCount; ++i) { + int id = timelines[i]->getPropertyId(); + if (_propertyIDs.contains(id)) { + timelineMode[i] = AnimationState::Subsequent; + } else { + _propertyIDs.add(id); + + if (to == NULL || !hasTimeline(to, id)) { + timelineMode[i] = AnimationState::First; + } else { + for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { + if (hasTimeline(next, id)) continue; + if (entry->_mixDuration > 0) { + timelineMode[i] = AnimationState::HoldMix; + timelineHoldMix[i] = entry; + i++; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState::Hold; + } } } } + +bool AnimationState::hasTimeline(TrackEntry* entry, int inId) { + Vector &timelines = entry->_animation->_timelines; + for (size_t i = 0, n = timelines.size(); i < n; ++i) { + if (timelines[i]->getPropertyId() == inId) { + return true; + } + } + return false; +}