summaryrefslogtreecommitdiff
path: root/Runtime/Animation/AnimationState.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Animation/AnimationState.cpp')
-rw-r--r--Runtime/Animation/AnimationState.cpp824
1 files changed, 824 insertions, 0 deletions
diff --git a/Runtime/Animation/AnimationState.cpp b/Runtime/Animation/AnimationState.cpp
new file mode 100644
index 0000000..d5efe04
--- /dev/null
+++ b/Runtime/Animation/AnimationState.cpp
@@ -0,0 +1,824 @@
+#include "UnityPrefix.h"
+#include "AnimationState.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "AnimationClip.h"
+#include "AnimationEvent.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+PROFILER_INFORMATION (gAddMixingTransform, "Animation.AddMixingTransform [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_INFORMATION (gRemoveMixingTransform, "Animation.RemoveMixingTransform [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_INFORMATION (gModifyAnimationClip, "Animation.ModifyAnimationClip [Triggers RebuildInternalState]", kProfilerAnimation);
+
+void AnimationState::InitializeClass ()
+{
+ AnimationClip::SetDidModifyClipCallback(DidModifyAnimationClip);
+}
+
+void AnimationState::CleanupClass ()
+{
+ AnimationClip::SetDidModifyClipCallback(NULL);
+}
+
+AnimationState::AnimationState ()
+: m_AnimationClipNode (this)
+{
+ // We use only 2 bits for m_AnimationEventState
+ // This assert guards against kAnimationEventStates running out of these bits
+ Assert(kAnimationEventState__Count <= 4);
+
+ m_Clip = NULL;
+ m_Curves = NULL;
+ m_IsClone = false;
+}
+
+AnimationState::~AnimationState ()
+{
+ m_Clip = NULL;
+ m_MixingTransforms.clear();
+ m_Name.clear();
+ CleanupCurves ();
+ m_AnimationClipNode.RemoveFromList();
+}
+
+void AnimationState::AllocateCurves(int count)
+{
+ AssertIf(m_Curves);
+ m_OwnsCurves = 1;
+
+ m_Curves = new AnimationCurveBase*[count];
+ for (int i=0;i<count;i++)
+ m_Curves[i] = NULL;
+}
+
+
+void AnimationState::SetClonedCurves(AnimationState& state)
+{
+ AssertIf(m_Curves);
+ m_OwnsCurves = 0;
+
+ m_Curves = state.m_Curves;
+// m_CurvesCount = state.m_CurvesCount;
+}
+
+
+void AnimationState::CleanupCurves ()
+{
+ if (!m_OwnsCurves || m_Curves == NULL)
+ {
+ m_Curves = NULL;
+ return;
+ }
+
+ delete[] m_Curves;
+ m_Curves = NULL;
+}
+
+void AnimationState::SetWeightTarget (float target, float length, bool stopWhenFaded)
+{
+ // TODO : this m_WeightDelta approach doesn't work very well when length is 0
+ // Current approach might lead to precision problems.
+ // The blend should happen instantly, but with very small deltaTimes it might need several frames.
+
+ AssertFiniteParameter(target);
+
+ float newWeightDelta;
+ if (length > 0.001)
+ newWeightDelta = (target - m_Weight) / length;
+ else
+ newWeightDelta = (target - m_Weight) * 100000.0F;
+
+ // If the current weight delta is going to reach the target faster than the new one, don't update it!
+ bool ignoreWeightDelta = m_FadeBlend && CompareApproximately(m_WeightTarget, target, kReallySmallWeight) && Abs(m_WeightDelta) > Abs(newWeightDelta);
+ if (!ignoreWeightDelta)
+ m_WeightDelta = newWeightDelta;
+
+ // We need to make sure that the weight delta is never zero, otherwise the stop condition in UpdateAnimationState is never reached!
+ if (CompareApproximately(m_WeightDelta, 0.0F, kReallySmallWeight))
+ m_WeightDelta = 100000.0F;
+
+ m_WeightTarget = target;
+
+ m_FadeBlend = true;
+ m_StopWhenFadedOut = stopWhenFaded;
+ m_IsFadingOut = false;
+}
+
+void AnimationState::SetWeightTargetImmediate (float target, bool stopWhenFaded)
+{
+ AssertFiniteParameter(target);
+
+ m_Weight = target;
+ m_StopWhenFadedOut = stopWhenFaded;
+ m_FadeBlend = false;
+ m_IsFadingOut = false;
+}
+
+
+///@TODO: Doesn't using the stop time conflict with using stop time for something else, like when queueing?
+void AnimationState::SetupFadeout (float length)
+{
+ m_FadeOutLength = length;
+}
+
+/*
+void AnimationState::ApplyWeightDeltaFraction ()
+{
+
+}
+*/
+/*
+void AnimationState::Delay (float time)
+{
+
+}
+*/
+
+void AnimationState::SetTime (float time)
+{
+ AssertFiniteParameter(time);
+ m_Time = time;
+ m_WrappedTime = WrapTime(time, m_CachedRange, m_WrapMode);
+ DebugAssertIf(!IsFinite(m_Time) || !IsFinite(m_WrappedTime));
+ m_AnimationEventState = kAnimationEventState_Search; // Re-search for animation event index
+}
+
+float WrapTime (float curveT, const std::pair<float, float>& range, int m_WrapMode)
+{
+ float begTime = range.first;
+ float endTime = range.second;
+
+ if (curveT >= endTime)
+ {
+ if (m_WrapMode == kRepeat)
+ curveT = Repeat (curveT, begTime, endTime);
+ else if (m_WrapMode == kClamp || m_WrapMode == kClampForever)
+ {
+ curveT = endTime;
+ }
+ else if (m_WrapMode == kPingPong)
+ curveT = PingPong (curveT, begTime, endTime);
+ }
+ else if (curveT < begTime)
+ {
+ if (m_WrapMode == kRepeat)
+ curveT = Repeat (curveT, begTime, endTime);
+ if (m_WrapMode == kClamp || m_WrapMode == kClampForever)
+ curveT = begTime;
+ else if (m_WrapMode == kPingPong)
+ curveT = PingPong (curveT, begTime, endTime);
+ }
+ return curveT;
+}
+
+namespace
+{
+ // returns -1, 0, 1 depending on sign/value of v
+ int GetDirection(float v)
+ {
+ if (v == 0) return 0;
+ else return v > 0 ? 1 : -1;
+ }
+}
+
+void AnimationState::SetSpeed (float speed)
+{
+ // When reversing speed. Recalculate animation event index
+ if (m_AnimationEventState == kAnimationEventState_PausedOnEvent)
+ {
+ // Handling special case when speed=0 and m_AnimationEventIndex is valid,
+ // bug the event that m_AnimationEventIndex is pointing to has been triggered before pause,
+ // so wee need to trigger event on the left or on the right
+ Assert(GetDirection(m_Speed) == 0);
+
+ int newSpeedDirection = GetDirection(speed);
+ if (newSpeedDirection != 0)
+ {
+ // In PinPong mode event are sometimes executed in the oposite direction of speed
+ // so we need to reverse newSpeedDirection based on that
+ if (m_WrapMode == kPingPong)
+ {
+ const float begTime = m_CachedRange.first;
+ const float endTime = m_CachedRange.second;
+
+ // Get index of how many times we are playing this back and forth
+ const int newWrapIndex = FloorfToInt((m_Time - begTime) / (endTime - begTime));
+
+ // Switch going forward based on the PingPong direction
+ if (newWrapIndex % 2 != 0)
+ newSpeedDirection = -newSpeedDirection;
+ }
+
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ m_AnimationEventIndex += newSpeedDirection > 0 ? 1 : -1;
+ }
+ }
+ else
+ {
+ const int oldSpeedDirection = GetDirection(m_Speed);
+ const int newSpeedDirection = GetDirection(speed);
+
+ // TODO : if we want a minor optimization, we could not trigger search
+ // if state is paused and then resumed in same direction, but triggering
+ // search should work just fine as long as we're not on event
+ if (oldSpeedDirection != newSpeedDirection)
+ m_AnimationEventState = kAnimationEventState_Search;
+ }
+
+ m_SyncedSpeed = m_Speed = speed;
+ AssertFiniteParameter(speed);
+
+ if (UseUnity32AnimationFixes())
+ SetupStopTime();
+}
+
+// offsetTime - time from which newWrappedTime starts (from which newWrappedTime is wrapped)
+// it is used for recalculating m_Time when event modifies playback direction
+bool AnimationState::FireEvents (const float deltaTime, float newWrappedTime, bool forward, Unity::Component& animation, const float beginTime, const float offsetTime, const bool reverseOffsetTime)
+{
+ AnimationClip::Events& events = m_Clip->GetEvents();
+
+ // Find initial event
+ if (m_AnimationEventState == kAnimationEventState_Search)
+ {
+ const float oldWrappedTime = m_WrappedTime;
+
+ if (forward)
+ {
+ AssertIf(oldWrappedTime > newWrappedTime);
+
+ for (int i=0;i<events.size();i++)
+ {
+ if (events[i].time >= oldWrappedTime)
+ {
+ m_AnimationEventIndex = i;
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ break;
+ }
+ }
+ }
+ else
+ {
+ AssertIf(oldWrappedTime < newWrappedTime);
+
+ for (int i=events.size()-1;i>=0;i--)
+ {
+ if (events[i].time <= oldWrappedTime)
+ {
+ m_AnimationEventIndex = i;
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ break;
+ }
+ }
+ }
+
+ if (m_AnimationEventState == kAnimationEventState_Search)
+ m_AnimationEventState = kAnimationEventState_NotFound;
+ }
+
+ const float oldSyncedSpeed = m_SyncedSpeed;
+ const float oldWrappedTime = m_WrappedTime;
+
+ while (true)
+ {
+ if (m_AnimationEventIndex < 0 || m_AnimationEventIndex >= events.size())
+ break;
+
+ float eventTime = events[m_AnimationEventIndex].time;
+
+ if (forward && eventTime > newWrappedTime)
+ break;
+ if (!forward && eventTime < newWrappedTime)
+ break;
+
+ const int currentAnimationEventIndex = m_AnimationEventIndex;
+
+ FireEvent (events[m_AnimationEventIndex], this, animation);
+ DebugAssertIf(!IsFinite(m_Time));
+ DebugAssertIf(!IsFinite(m_WrappedTime));
+
+ if (m_AnimationEventState == kAnimationEventState_Search)
+ {
+ const int oldSpeedDirection = GetDirection(oldSyncedSpeed);
+ const int newSpeedDirection = GetDirection(m_SyncedSpeed);
+
+ // handling special case when speed direction was changed
+ // we want to continue time from eventTime and events from next event (we do not want to trigger this event immediately again)
+ if (oldSpeedDirection != newSpeedDirection)
+ {
+ // do not modify m_WrappedTime if it has been modified inside of event
+ if (m_WrappedTime == oldWrappedTime)
+ {
+ const float timeDelta = eventTime - beginTime;
+ float newTime = offsetTime + (reverseOffsetTime ? -timeDelta : timeDelta);
+ AssertMsg(Abs(newTime - m_Time) <= deltaTime + std::numeric_limits<float>::epsilon(), "Abs(%f - %f) <= %f\n%f <= %f\n%e <= 0", newTime, m_Time, deltaTime, Abs(newTime - m_Time), deltaTime, Abs(newTime - m_Time) - deltaTime);
+ m_Time = newTime;
+
+ m_WrappedTime = eventTime;
+
+ if (newSpeedDirection == 0)
+ {
+ // if time is paused we need to continue from "right" or "left" event
+ // after time is unpaused, but we will know next event only when time is unpaused,
+ // so we set AnimationEventState to PausedOnEvent.
+ // We can just set state to Searh, because it would find and trigger same event
+ m_AnimationEventIndex = currentAnimationEventIndex;
+ m_AnimationEventState = kAnimationEventState_PausedOnEvent;
+ }
+ else
+ {
+ // if time is reversed we need to continue from "right" or "left" event
+ m_AnimationEventIndex = currentAnimationEventIndex + (!forward ? 1 : -1);
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ }
+ }
+ }
+
+ DebugAssertIf(!IsFinite(m_Time) || !IsFinite(m_WrappedTime));
+ return false;
+ }
+
+ if (forward)
+ m_AnimationEventIndex++;
+ else
+ m_AnimationEventIndex--;
+ }
+
+ return true;
+}
+
+
+bool AnimationState::UpdateAnimationState (double globalTime, Unity::Component& animationComponent)
+{
+ DebugAssertIf(!IsFinite(m_Time) || !IsFinite(m_WrappedTime));
+
+ // Update time
+ const float deltaTime = globalTime - m_LastGlobalTime;
+ m_LastGlobalTime = globalTime;
+
+ //deltaTime = 0.166667f;
+
+ //LogString(Format("globalTime: %f; deltaTime: %f; m_Time: %f", globalTime, deltaTime, m_Time));
+
+ float syncedSpeedDeltaTime = deltaTime * m_SyncedSpeed;
+ // Do not trigger events if time is stopped
+ if (syncedSpeedDeltaTime != 0)
+ {
+ double lastTime = m_Time;
+ m_Time += syncedSpeedDeltaTime;
+ // if syncedSpeedDeltaTime is big it might cross whole loop,
+ // so we would have to fire all events in that loop, but we ignore cases like this for now...
+ float newWrappedTime = m_WrappedTime + syncedSpeedDeltaTime;
+ const float oldWrappedTime = m_WrappedTime;
+
+
+ /// Wrap time and Fire Animation Events
+ float begTime = m_CachedRange.first;
+ float endTime = m_CachedRange.second;
+
+ const bool forward = m_SyncedSpeed >= 0.0F;
+
+ ///@TODO: How should repeat behave when the first key is not at zero???
+ /// - Animations should always start playback at zero and also go back to zero, regardless of where the first frame is. (Rune)
+ ///@TODO: How do we stop the animation when we play backwards?
+ /// - The animation should stop when it has reached zero. See functional test AnimationOnceSamplesAtEndAndResetsTimeAndGetsDisabled (Rune)
+ ///@TODO: Repeat currently doesnt start at the first key frame it might enter anywhere depending on begin / end Time (Repeat (m_Time, begTime, endTime);)
+ /// - The animation should not go back to the exact start of the range when looping, but rather subtract the range length. (Rune)
+ /// - However, the range should always start at zero, not at the first key. (Rune)
+
+ // Repeat
+ if (m_WrapMode == kRepeat)
+ {
+ // Reached end of time range - wrap around and fire animation events
+ if (newWrappedTime >= endTime)
+ {
+ newWrappedTime = RepeatD (m_Time, begTime, endTime);
+ if (m_HasAnimationEvent)
+ {
+ float offsetTime = m_Time - (newWrappedTime - begTime) - (endTime - begTime);
+ if (FireEvents (deltaTime, endTime, forward, animationComponent, begTime, offsetTime, false))
+ {
+ offsetTime += (endTime - begTime);
+ m_AnimationEventIndex = 0;
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ FireEvents (deltaTime, newWrappedTime, forward, animationComponent, begTime, offsetTime, false);
+ }
+ }
+ }
+ else if (newWrappedTime < begTime)
+ {
+ newWrappedTime = RepeatD (m_Time, begTime, endTime);
+ if (m_HasAnimationEvent)
+ {
+ float offsetTime = m_Time + (endTime - newWrappedTime);
+ if (FireEvents (deltaTime, begTime, forward, animationComponent, begTime, offsetTime, false))
+ {
+ offsetTime -= (endTime - begTime);
+ m_AnimationEventIndex = m_Clip->GetEvents().size() - 1;
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ FireEvents (deltaTime, newWrappedTime, forward, animationComponent, begTime, offsetTime, false);
+ }
+ }
+ }
+ // Inside of begin / end time range -> Fire animation event only
+ else if (m_HasAnimationEvent)
+ {
+ const float offsetTime = m_Time - (newWrappedTime - begTime);
+ FireEvents (deltaTime, newWrappedTime, forward, animationComponent, begTime, offsetTime, false);
+ }
+
+
+ // It's important to used RepeatD, because we get an assert otherwise (for exmaple: 1.9999999 double is rounded to 2.0f, which results in 0 when wrapped)
+ DebugAssertIf(m_WrappedTime == oldWrappedTime && !CompareApproximately(newWrappedTime, RepeatD (m_Time, begTime, endTime), 0.01F));
+ }
+ // Clamp
+ else if (m_WrapMode == kClamp || m_WrapMode == kClampForever)
+ {
+ if (m_Time < begTime)
+ newWrappedTime = begTime;
+ else if (m_Time > endTime)
+ newWrappedTime = endTime;
+ else
+ newWrappedTime = m_Time;
+
+ if (m_HasAnimationEvent)
+ {
+ FireEvents (deltaTime, m_Time, forward, animationComponent, begTime, begTime, false);
+ }
+ }
+ // Default
+ else if (m_WrapMode == kDefaultWrapMode)
+ {
+ if (m_HasAnimationEvent)
+ {
+ FireEvents (deltaTime, newWrappedTime, forward, animationComponent, begTime, begTime, false);
+ }
+ }
+ // Ping Pong
+ else if (m_WrapMode == kPingPong)
+ {
+ newWrappedTime = PingPong(m_Time, begTime, endTime);
+
+ if (m_HasAnimationEvent)
+ {
+ const AnimationClip::Events& events = m_Clip->GetEvents();
+
+ AssertIf(Abs(endTime - begTime) < std::numeric_limits<float>::epsilon());
+
+ // Get index of how many times we are playing this back and forth
+ int wrapIndex = FloorfToInt((lastTime - begTime) / (endTime - begTime));
+ int newWrapIndex = FloorfToInt((m_Time - begTime) / (endTime - begTime));
+
+ bool forwardPlayback = m_SyncedSpeed >= 0.0F;
+
+ // Switch going forward based on the pingpong direction
+ if (newWrapIndex % 2 != 0)
+ forwardPlayback = !forwardPlayback;
+
+ // Inside of begin / end boundary
+ if (wrapIndex == newWrapIndex)
+ {
+ const float offsetTime = m_Time - (newWrappedTime - begTime);
+ FireEvents (deltaTime, newWrappedTime, forwardPlayback, animationComponent, begTime, offsetTime, false);
+ }
+ // Crossing boundary
+ else
+ {
+ if (forwardPlayback)
+ {
+ float offsetTime = forward ?
+ m_Time - (newWrappedTime - begTime) :
+ m_Time + (newWrappedTime - begTime);
+ if (FireEvents (deltaTime, begTime, false, animationComponent, begTime, offsetTime, forward))
+ {
+ // if event is right at the begin time - we want to play it once, otherwise twice
+ // we might get m_AnimationEventIndex=events.size() in this statement, but that's acceptable situation
+ // it indicates that there are no events until we reverse the time
+ m_AnimationEventIndex = (events.front().time == begTime ? 1 : 0);
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ // Events should never be searched in next call of FireEvents
+ FireEvents (deltaTime, newWrappedTime, true, animationComponent, endTime, offsetTime, !forward);
+ }
+ }
+ else
+ {
+ float offsetTime = forward ?
+ m_Time - (endTime - newWrappedTime) :
+ m_Time + (endTime - newWrappedTime);
+ if (FireEvents (deltaTime, endTime, true, animationComponent, endTime, offsetTime, !forward))
+ {
+ // if event is right at the end time - we want to play it once, otherwise twice
+ // we might get m_AnimationEventIndex=-1 in this statement, but that's acceptable situation
+ // it indicates that there are no events until we reverse the time
+ m_AnimationEventIndex = events.size() - (events.back().time == endTime ? 2 : 1);
+ m_AnimationEventState = kAnimationEventState_HasEvent;
+ // Events should never be searched in next call of FireEvents
+ FireEvents (deltaTime, newWrappedTime, false, animationComponent, begTime, offsetTime, forward);
+ }
+ }
+ }
+ }
+ }
+ else
+ ErrorString("Unknown wrapMode");
+
+ DebugAssertIf(!IsFinite(newWrappedTime));
+
+ // do not set m_WrappedTime if it was altered in one of events
+ if (m_WrappedTime == oldWrappedTime)
+ m_WrappedTime = newWrappedTime;
+ DebugAssertIf(!IsFinite(m_Time) || !IsFinite(m_WrappedTime));
+ }
+
+ return UseUnity32AnimationFixes() ? UpdateFading(deltaTime) : UpdateFading_Before32(deltaTime);
+}
+
+// This is for backwards compatibility. If you need to make changes,
+// then make them in UpdateFading which is used with Unity 3.2 and later content
+bool AnimationState::UpdateFading_Before32(float deltaTime)
+{
+ // We are now fading out!
+ if (m_Time > m_StopTime - m_FadeOutLength && !m_IsFadingOut)
+ {
+ SetWeightTarget(0.0F, m_FadeOutLength, true);
+ m_IsFadingOut = true;
+
+ ///@TODO: Apply fractional delta based on current time!
+ // A fadeout should have been started at m_StopTime - m_FadeOutLength but we exceeded it by some time
+ // We should apply the fraction by what we exceeded it to the weight!
+ // ApplyWeightDeltaFraction(m_Time - m_FadeOutLength);
+ }
+
+ bool didStopAtEnd = false;
+ // Update blend target
+ if (m_FadeBlend)
+ didStopAtEnd = UpdateBlendingWeight(deltaTime, false);
+
+ return didStopAtEnd;
+}
+
+bool AnimationState::UpdateFading(float deltaTime)
+{
+ bool didStopAtEnd = false;
+
+ // We are now fading out!
+ if (!m_IsFadingOut && UseStopTime())
+ {
+ Assert(m_FadeOutLength >= 0);
+
+ const bool forward = m_Speed >= 0;
+ const float dt = forward ? m_Time - (m_StopTime - m_FadeOutLength) : (m_StopTime + m_FadeOutLength) - m_Time;
+ if (dt > 0)
+ {
+ SetWeightTarget(0.0F, m_FadeOutLength, true);
+ m_IsFadingOut = true;
+
+ if (UseUnity35AnimationFixes())
+ {
+ // Applying fractional delta based on current time!
+ // A fadeout has been started at m_StopTime - m_FadeOutLength but we exceeded it by some time,
+ // so we need to apply delta on m_Weight
+ didStopAtEnd = UpdateBlendingWeight(dt, m_FadeOutLength == 0);
+ }
+ }
+ }
+
+ // Update blend target
+ if (m_FadeBlend)
+ didStopAtEnd = UpdateBlendingWeight(deltaTime, false);
+
+ return didStopAtEnd;
+}
+
+bool AnimationState::UpdateBlendingWeight(const float deltaTime, const bool instantBlend)
+{
+ bool didStopAtEnd = false;
+
+ m_Weight += deltaTime * m_WeightDelta;
+
+ // Stop blending and clamp when we reach the target
+ if (instantBlend ||
+ (m_WeightDelta > 0.0F && m_Weight > m_WeightTarget) ||
+ (m_WeightDelta <= 0.0F && m_Weight < m_WeightTarget))
+ {
+ m_Weight = m_WeightTarget;
+ m_FadeBlend = 0;
+ m_IsFadingOut = 0;
+ if (m_StopWhenFadedOut)
+ {
+ m_UnstoppedLastWrappedTime = m_WrappedTime;
+ Stop();
+ didStopAtEnd = true;
+ }
+ }
+ else
+ {
+ #if !UNITY_RELEASE
+ AssertMsg((m_WrapMode != kDefaultWrapMode && m_WrapMode != kClamp) || m_Time < GetLength() + 0.05F, "Time is out of range: %f < %f", m_Time, GetLength());
+ #endif
+ }
+
+ return didStopAtEnd;
+}
+
+void AnimationState::DidModifyAnimationClip (AnimationClip* clip, AnimationStateList& states)
+{
+ AnimationStateList::iterator i;
+ for (i=states.begin();i!=states.end();i++)
+ {
+ AnimationState& state = **i;
+ if (clip == NULL)
+ {
+ state.m_Clip = NULL;
+ state.m_HasAnimationEvent = 0;
+ }
+ else
+ {
+ AssertIf (state.m_Clip != clip);
+ state.m_CachedRange = state.m_Clip->GetRange();
+ AssertIf(!IsFinite(state.m_CachedRange.first) || !IsFinite(state.m_CachedRange.second));
+ state.m_HasAnimationEvent = !state.m_Clip->GetEvents().empty();
+ }
+
+ PROFILER_AUTO(gModifyAnimationClip, NULL)
+
+ state.m_DirtyMask |= kRebindDirtyMask;
+ }
+
+ if (clip == NULL)
+ states.clear();
+}
+
+///@TODO: Import pipeline should allow reimporting clips while in playmode. For this we just need to make them reuse the animation clip asset
+/// and call DidChangeClip
+
+void AnimationState::Init(const UnityStr& name, AnimationClip* clip, double globalTime, int wrap, bool isClone )
+{
+ AssertIf (m_Clip);
+ AssertIf (m_Curves);
+ AssertIf (m_AnimationClipNode.IsInList());
+
+ m_IsClone = isClone;
+ m_Clip = clip;
+ m_HasAnimationEvent = 0;
+ if (m_Clip)
+ {
+ m_CachedRange = m_Clip->GetRange();
+ AssertIf(!IsFinite(m_CachedRange.first) || !IsFinite(m_CachedRange.second));
+ m_Clip->AddAnimationState(m_AnimationClipNode);
+ m_Name = name;
+ m_HasAnimationEvent = !m_Clip->GetEvents().empty();
+ }
+
+ m_BlendMode = kBlend;
+ m_Weight = 0.0F;
+ m_FadeBlend = 0;
+ m_StopWhenFadedOut = 0;
+ m_IsFadingOut = 0;
+ m_AutoCleanup = 0;
+ m_ShouldCleanup = 0;
+ m_FadeOutLength = 0.0F;
+ m_WrappedTime = 0.0F;
+ m_AnimationEventIndex = -1;
+ m_AnimationEventState = kAnimationEventState_Search;
+ m_Time = 0;
+// m_WeightDelta = 0.0F;
+// m_UnstoppedLastWeight = 0.0F;
+// m_UnstoppedLastWrappedTime = 0.0F;
+// m_WeightTarget = 1.0F;
+// m_WeightDelta = 0.0F;
+ m_LastGlobalTime = globalTime;
+
+ m_Layer = 0;
+ m_SyncedSpeed = m_Speed = 1.0F;
+ m_Enabled = false;
+
+ SetWrapMode(wrap);
+ SetTime(0.0F);
+
+// m_GlobalStopTime = std::numeric_limits<float>::infinity();
+
+ m_DirtyMask = kRebindDirtyMask | kLayersDirtyMask;
+}
+
+void AnimationState::SetupStopTime()
+{
+ bool forward = UseUnity32AnimationFixes() ? m_Speed >= 0 : true;
+
+ m_StopTime = UseStopTime() ?
+ (forward ? m_CachedRange.second : m_CachedRange.first) :
+ (forward ? std::numeric_limits<float>::infinity() : -std::numeric_limits<float>::infinity());
+}
+
+void AnimationState::SetWrapMode (int wrap)
+{
+ m_WrapMode = wrap;
+
+ // we need to update m_WrappedTime, because we can set time first and then wrap mode
+ m_WrappedTime = WrapTime(m_Time, m_CachedRange, m_WrapMode);
+ DebugAssertIf(!IsFinite(m_Time) || !IsFinite(m_WrappedTime));
+
+ SetupStopTime();
+}
+
+void AnimationState::SetEnabled (bool enabled)
+{
+ if (enabled && !m_Enabled)
+ m_LastGlobalTime = GetCurTime();
+ m_Enabled = enabled;
+}
+
+void AnimationState::AddMixingTransform(Transform& transform, bool recursive)
+{
+ m_MixingTransforms.insert(std::make_pair(PPtr<Transform> (&transform), recursive));
+ m_DirtyMask |= kRebindDirtyMask;
+
+ PROFILER_AUTO(gAddMixingTransform, NULL)
+}
+
+void AnimationState::RemoveMixingTransform(Transform& transform)
+{
+ MixingTransforms::iterator it = m_MixingTransforms.find(PPtr<Transform>(&transform));
+ if (it != m_MixingTransforms.end())
+ m_MixingTransforms.erase(it);
+ else
+ {
+ ErrorStringMsg("RemoveMixingTransform couldn't find transform '%s' in a list of mixing transforms. "
+ "You can only remove transforms that have been added through AddMixingTransform", transform.GetName());
+ }
+ m_DirtyMask |= kRebindDirtyMask;
+
+ PROFILER_AUTO(gRemoveMixingTransform, NULL)
+}
+
+
+// TODO : this looks a bit expensive: it's iterating hierarchy every time
+bool AnimationState::ShouldMixTransform (Transform& transform)
+{
+ if (m_MixingTransforms.empty())
+ return true;
+
+ for (MixingTransforms::iterator i=m_MixingTransforms.begin();i != m_MixingTransforms.end();i++)
+ {
+ if (i->second)
+ {
+ Transform* root = i->first;
+ if (root && IsChildOrSameTransform(transform, *root))
+ return true;
+ }
+ else
+ {
+ if (i->first == PPtr<Transform> (&transform))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void AnimationState::Stop ()
+{
+ if ( m_Enabled && m_AutoCleanup )
+ m_ShouldCleanup = true;
+
+ m_Enabled = false;
+ SetTime(0.0F);
+ m_FadeBlend = 0;
+ m_StopWhenFadedOut = 0;
+}
+
+void AnimationState::SetupUnstoppedState()
+{
+ std::swap(m_WrappedTime, m_UnstoppedLastWrappedTime);
+ m_UnstoppedLastWeight = m_Weight;
+ // HACK: VERY ugly way of making sure the last frame of an animation gets accounted into the final position. was 1.0f but this
+ // gave problems when crossfading since the state that should no longer contribute, suddently had a weight of 1 for one frame
+ // We need to redo some of the logic in here at some point
+ m_Weight = kReallySmallWeight*1.001f;
+ m_Enabled = true;
+}
+
+void AnimationState::CleanupUnstoppedState()
+{
+ std::swap(m_WrappedTime, m_UnstoppedLastWrappedTime);
+ m_Weight = m_UnstoppedLastWeight;
+ m_Enabled = false;
+}
+
+bool AnimationState::UseUnity32AnimationFixes()
+{
+ return IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_2_a1);
+}
+
+bool AnimationState::UseUnity34AnimationFixes()
+{
+ return IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_4_a1);
+}
+
+bool AnimationState::UseUnity35AnimationFixes()
+{
+ return IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_5_a1);
+}
+