summaryrefslogtreecommitdiff
path: root/Runtime/Animation/Animation.cpp
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2019-08-14 22:50:43 +0800
committerchai <chaifix@163.com>2019-08-14 22:50:43 +0800
commit15740faf9fe9fe4be08965098bbf2947e096aeeb (patch)
treea730ec236656cc8cab5b13f088adfaed6bb218fb /Runtime/Animation/Animation.cpp
+Unity Runtime codeHEADmaster
Diffstat (limited to 'Runtime/Animation/Animation.cpp')
-rw-r--r--Runtime/Animation/Animation.cpp2373
1 files changed, 2373 insertions, 0 deletions
diff --git a/Runtime/Animation/Animation.cpp b/Runtime/Animation/Animation.cpp
new file mode 100644
index 0000000..a2d7cda
--- /dev/null
+++ b/Runtime/Animation/Animation.cpp
@@ -0,0 +1,2373 @@
+#include "UnityPrefix.h"
+#include "Animation.h"
+#include "AnimationManager.h"
+#include "AnimationClip.h"
+#include "AnimationClipUtility.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Utilities/Utility.h"
+#include "AnimationBinder.h"
+#include "NewAnimationTrack.h"
+#include "AnimationState.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Shaders/Material.h"
+#include "AnimationCurveUtility.h"
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Utilities/dense_hash_map.h"
+#include "Runtime/Utilities/Prefetch.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Animation/AnimationUtility.h"
+#include "Runtime/BaseClasses/EventIDs.h"
+
+using std::make_pair;
+using std::max;
+
+PROFILER_INFORMATION (gBuildAnimationState, "Animation.RebuildInternalState", kProfilerAnimation);
+PROFILER_INFORMATION (gUpdateAnimation, "Animation.Update", kProfilerAnimation);
+PROFILER_INFORMATION (gSampleAnimation, "Animation.Sample", kProfilerAnimation);
+PROFILER_WARNING (gDidDestroyObjectNotification, "Animation.DestroyAnimationClip [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_WARNING (gAddClip, "Animation.AddClip [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_WARNING (gRemoveClip, "Animation.RemoveClip [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_WARNING (gCloneAnimationState, "Animation.Clone [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_WARNING (gAnimationDeactivate, "Animation.Deactivate [Triggers RebuildInternalState]", kProfilerAnimation);
+PROFILER_INFORMATION (gValidate, "ValidateBoundCurves", kProfilerAnimation);
+
+inline int CombineWrapMode (int clipWrapMode, int animationWrapMode)
+{
+ if (clipWrapMode != 0)
+ return clipWrapMode;
+ else
+ return animationWrapMode;
+}
+
+Animation::Animation (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_AnimationManagerNode(this)
+, m_ActiveAnimationStatesSize(0)
+, m_CullingType(kCulling_AlwaysAnimate)
+, m_BoundCurves (label)
+{
+ m_WrapMode = 0;
+
+ m_PlayAutomatically = true;
+ m_AnimatePhysics = false;
+ m_DirtyMask = 0;
+ m_Visible = false;
+ memset (m_ActiveAnimationStates, 0, sizeof(m_ActiveAnimationStates));
+}
+
+Animation::~Animation ()
+{
+ ClearContainedRenderers ();
+ ReleaseAnimationStates ();
+ CleanupBoundCurves();
+}
+
+
+void Animation::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+
+#if UNITY_EDITOR
+ LoadOldAnimations();
+#endif
+
+ // We don't know what kind of culling we used before, so we clear all culling related data
+ ClearContainedRenderers();
+
+ CheckIsCullingBasedOnBoundsDeprecated();
+
+ if (m_CullingType == kCulling_BasedOnRenderers && !m_AnimationStates.empty())
+ RecomputeContainedRenderers ();
+
+ if (m_PlayAutomatically && (awakeMode & (kDidLoadFromDisk | kInstantiateOrCreateFromCodeAwakeFromLoad | kActivateAwakeFromLoad)) && IsActive () && IsWorldPlaying())
+ Play (kStopAll);
+}
+
+void Animation::Deactivate (DeactivateOperation operation)
+{
+ if (operation != kDeprecatedDeactivateToggleForLevelLoad)
+ {
+ PROFILER_AUTO(gAnimationDeactivate, this)
+ //ReleaseAnimationStates ();
+ Stop();
+ CleanupBoundCurves();
+ }
+}
+
+AnimationClip* Animation::GetClipWithNameSerialized (const std::string& name)
+{
+ for (Animations::iterator i=m_Animations.begin();i != m_Animations.end();i++)
+ {
+ AnimationClip* clip = *i;
+ if (clip && clip->GetName() == name)
+ return clip;
+ }
+ return NULL;
+}
+
+void Animation::SetClip (PPtr<AnimationClip> anim)
+{
+ m_Animation = anim;
+
+ SetDirty ();
+}
+
+void Animation::SetClips (const Animations& anims)
+{
+ m_Animations = anims;
+
+ CheckIsCullingBasedOnBoundsDeprecated();
+
+ SetDirty ();
+}
+
+bool Animation::IsPlayingLayer (int layer)
+{
+ if (m_AnimationStates.empty ())
+ return false;
+
+ for (iterator i=begin();i != end();i++)
+ {
+ AnimationState& state = **i;
+ if (state.GetLayer() == layer && state.GetEnabled())
+ return true;
+ }
+
+ return false;
+}
+
+
+bool Animation::IsPlaying ()
+{
+ if (m_AnimationStates.empty ())
+ return false;
+
+ for (iterator i=begin();i != end();i++)
+ {
+ AnimationState& state = **i;
+ if (state.GetEnabled())
+ return true;
+ }
+
+ return false;
+}
+
+bool Animation::IsPlaying (const string& clip)
+{
+ AnimationState* state = GetState(clip);
+
+ if ( state && state->GetEnabled() )
+ return true;
+ else // check for clones
+ {
+ for ( int s = 0; s < m_AnimationStates.size(); s++ )
+ {
+ AnimationState* st = m_AnimationStates[s];
+
+ if ( st->IsClone()
+ && st->GetParentName() == clip
+ && st->GetEnabled() )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+void Animation::Stop ()
+{
+ for (iterator i=begin();i != end();i++)
+ {
+ (**i).Stop();
+ }
+
+ // Remove all queued animations.
+ m_Queued.clear();
+}
+
+void Animation::Stop( const string& name )
+{
+ for ( int s = 0; s < m_AnimationStates.size(); s++ )
+ {
+ AnimationState* state = m_AnimationStates[s];
+
+ bool stopState = false;
+ if ( state->IsClone() && state->GetParentName() == name )
+ stopState = true;
+ else if ( !state->IsClone() && state->GetName() == name )
+ stopState = true;
+
+ if ( stopState )
+ Stop( *state );
+ }
+
+ // Remove any queued animations that were cloned from the named state.
+ QueuedAnimations::iterator q, qnext;
+ for (q=m_Queued.begin();q != m_Queued.end();q=qnext)
+ {
+ qnext = q;
+
+ if ( q->state->GetParentName() != name )
+ qnext++;
+ else
+ m_Queued.erase( q );
+ }
+
+}
+
+void Animation::Stop (AnimationState& state)
+{
+ state.Stop();
+}
+
+void Animation::Rewind ()
+{
+ for (iterator i=begin();i != end();i++)
+ {
+ Rewind(**i);
+ }
+}
+
+void Animation::Rewind (const string& name)
+{
+ if (!m_AnimationStates.empty())
+ {
+ AnimationState* state = GetState(name);
+ if (state)
+ Rewind(*state);
+ }
+}
+
+void Animation::Rewind (AnimationState& state)
+{
+ state.SetTime(0.0F);
+}
+
+void Animation::SetWrapMode (int mode)
+{
+ m_WrapMode = mode;
+ for (iterator i=begin ();i != end ();i++)
+ {
+ AnimationState& state = **i;
+ state.SetWrapMode(mode);
+ }
+ SetDirty();
+}
+
+#if UNITY_EDITOR
+/// Deprecated with 1.5
+void Animation::LoadOldAnimations ()
+{
+ // Load old animations into new animation array
+ for (OldAnimations::iterator i=m_OldAnimations.begin ();i != m_OldAnimations.end ();i++)
+ {
+ AnimationClip* clip = i->second;
+ if (clip)
+ {
+ AddClip (*clip, i->first, INT_MIN, INT_MAX, false);
+ }
+ }
+ m_OldAnimations.clear();
+
+
+ // Make sure we have the default animation in the animations list
+ AnimationClip* defaultAnim = m_Animation;
+ if (defaultAnim)
+ {
+ for (Animations::iterator i=m_Animations.begin();i!=m_Animations.end();i++)
+ {
+ if (*i == m_Animation)
+ return;
+ }
+
+ AddClip (*defaultAnim, defaultAnim->GetName(), INT_MIN, INT_MAX, false);
+ }
+}
+#endif
+
+IMPLEMENT_CLASS_HAS_INIT (Animation)
+IMPLEMENT_OBJECT_SERIALIZE (Animation)
+///////////////////***************************
+
+void Animation::AddToManager ()
+{
+ // This method is named AddToManager, but it is used to remove from manager too...
+ m_AnimationManagerNode.RemoveFromList();
+ if (IsWorldPlaying())
+ {
+ if (GetEnabled() && (m_Visible || m_CullingType == kCulling_AlwaysAnimate) && IsActive() && !m_AnimationStates.empty())
+ {
+ if (!m_AnimatePhysics)
+ GetAnimationManager().AddDynamic(m_AnimationManagerNode);
+ else
+ GetAnimationManager().AddFixed(m_AnimationManagerNode);
+ }
+ }
+ else
+ {
+ // Insert all in edit mode - for sampling animation with the timeline
+ if (IsActive())
+ GetAnimationManager().AddDynamic(m_AnimationManagerNode);
+ }
+}
+
+void Animation::RemoveFromManager ()
+{
+ m_AnimationManagerNode.RemoveFromList();
+}
+
+void Animation::SetAnimatePhysics (bool anim)
+{
+ m_AnimatePhysics = anim;
+ SetDirty();
+ if (m_AnimationManagerNode.IsInList())
+ {
+ m_AnimationManagerNode.RemoveFromList();
+ if (!m_AnimatePhysics)
+ GetAnimationManager().AddDynamic(m_AnimationManagerNode);
+ else
+ GetAnimationManager().AddFixed(m_AnimationManagerNode);
+ }
+}
+
+#define ScriptErrorStringObject ErrorStringObject
+
+const char* kAnimationNotFoundError =
+"The animation state %s could not be played because it couldn't be found!\n"
+"Please attach an animation clip with the name '%s' or call this function only for existing animations.";
+#define CANT_PLAY_ERROR { ScriptErrorStringObject(Format(kAnimationNotFoundError, name.c_str(), name.c_str()), this); }
+
+
+const char* kWrongStateError =
+"The animation state %s could not be played because it is not attached to the animation component!\n"
+"You have to provide an animation state that is attached to the same animation component.";
+#define WRONG_STATE_ERROR(x) { ScriptErrorStringObject(Format(kWrongStateError, x.GetName().c_str()), this); }
+
+
+bool Animation::Play(const std::string& name, int playMode)
+{
+ // Deprecated support for play with queueing
+ if (playMode == kPlayQueuedDeprecated)
+ {
+ QueueCrossFade(name, 0.0F, CompleteOthers, kStopSameLayer);
+ return true;
+ }
+
+ CrossFade(name, 0.0F, playMode);
+ return true;
+}
+
+void Animation::Play(AnimationState& fadeIn, int playMode)
+{
+ // Deprecated support for play with queueing
+ if (playMode == kPlayQueuedDeprecated)
+ {
+ QueueCrossFade(fadeIn, 0.0F, CompleteOthers, kStopSameLayer);
+ return;
+ }
+
+ CrossFade(fadeIn, 0.0F, playMode, true);
+ return;
+}
+
+
+bool Animation::Play (int mode)
+{
+ AnimationClip* clip = m_Animation;
+ if (clip)
+ {
+ AnimationState* state = GetState(clip);
+ if (state)
+ {
+ Play(*state, mode);
+ return true;
+ }
+ else
+ {
+ LogStringObject("Default clip could not be found in attached animations list.", this);
+ return false;
+ }
+ }
+ else
+ return false;
+}
+
+void Animation::Blend(const std::string& name, float targetWeight, float time) {
+ AnimationState* state = GetState(name);
+ if (state)
+ Blend(*state, targetWeight, time);
+ else
+ CANT_PLAY_ERROR
+}
+
+void Animation::CrossFade(const std::string& name, float time, int mode) {
+ AnimationState* state = GetState(name);
+ if (state)
+ CrossFade(*state, time, mode, true);
+ else
+ CANT_PLAY_ERROR
+}
+
+AnimationState* Animation::QueueCrossFade(const std::string& name, float time, int queue, int mode) {
+ AnimationState* state = GetState(name);
+ if (state)
+ return QueueCrossFade(*state, time, queue, mode);
+ else
+ {
+ CANT_PLAY_ERROR
+ return NULL;
+ }
+}
+
+/// - Should Blend and CrossFade Rewind animations before fading in?
+/// - Should CrossFade Rewind animations after they are faded out?
+
+void Animation::Blend(AnimationState& playState, float targetWeight, float time)
+{
+ bool found = false;
+ for (iterator i=begin();i!=end();i++)
+ {
+ AnimationState& state = **i;
+
+ if (&playState == &state)
+ {
+ state.SetEnabled(true);
+ state.SetWeightTarget(targetWeight, time, false);
+ state.SetupFadeout(time);
+ found = true;
+ }
+ }
+ if (!found)
+ WRONG_STATE_ERROR(playState)
+}
+
+void Animation::CrossFade(AnimationState& playState, float time, int mode, bool clearQueuedAnimations )
+{
+ bool found = false;
+ for (iterator i=begin();i!=end();i++)
+ {
+ AnimationState& state = **i;
+
+ // Don't touch animations in other layers!
+ if ((mode & kStopAll) == 0 && state.GetLayer() != playState.GetLayer())
+ continue;
+
+ if (&playState == &state)
+ {
+ state.SetEnabled(true);
+ if (time > kReallySmallFadeTime)
+ state.SetWeightTarget(1.0F, time, false);
+ else
+ {
+ state.SetWeightTargetImmediate(1.0F, false);
+ }
+
+ state.SetupFadeout(time);
+ found = true;
+ }
+ else
+ {
+ if (time > kReallySmallFadeTime)
+ state.SetWeightTarget(0.0F, time, true);
+ else
+ {
+ state.Stop();
+ state.SetWeight(0.0F);
+ }
+ }
+ }
+
+ if ( clearQueuedAnimations )
+ {
+ // Clear out queued animations on the same channel as the state
+ // we are cross fading to or all queued animations if we are
+ // stopping all.
+
+ // Fixed bug: https://fogbugz.unity3d.com/default.asp?470484, on metro std vector are somehow implemented differently
+ // If you remove an element from the list, you have to reinitialize all the iterators, to fix the problem, I removed iterators at all
+ for (int i = 0; i < m_Queued.size();)
+ {
+ if ( !(mode & kStopAll) && m_Queued[i].state->GetLayer() != playState.GetLayer() )
+ {
+ i++;
+ continue;
+ }
+
+ m_Queued[i].state->Stop();
+ m_Queued[i].state->ForceAutoCleanup();
+ m_Queued.erase(m_Queued.begin() + i);
+ }
+ }
+
+ if ( !found )
+ WRONG_STATE_ERROR(playState)
+}
+
+AnimationState* Animation::QueueCrossFade(AnimationState& originalState, float time, int queueMode, int mode)
+{
+ AnimationState* cloned = CloneAnimation(&originalState);
+ if (!cloned)
+ {
+ WRONG_STATE_ERROR(originalState)
+ return NULL;
+ }
+
+ AnimationState& playState = *cloned;
+ playState.SetAutoCleanup();
+
+ // Queue the animation for real!
+ if (queueMode == CompleteOthers)
+ {
+ QueuedAnimation queue;
+ queue.mode = mode;
+ queue.queue = queueMode;
+ queue.fadeTime = time;
+ queue.state = &playState;
+ m_Queued.push_back(queue);
+ return &playState;
+ }
+ else
+ {
+ CrossFade(playState, time, mode, true);
+ return &playState;
+ }
+}
+
+/// * All animation states have an array with pointers to the curves used when sampling. (AnimationState.m_Curves)
+/// The index in the AnimationState.m_Curves is the same as the BoundCurves index.
+/// If a curve is not available or excluded because of mixing the pointer is NULL.
+
+void InsertAnimationClipCurveIDs(AnimationBinder::CurveIDLookup& curveIDLookup, AnimationClip& clip)
+{
+ AnimationClip::QuaternionCurves& rot = clip.GetRotationCurves();
+ AnimationClip::Vector3Curves& pos = clip.GetPositionCurves();
+ AnimationClip::Vector3Curves& scale = clip.GetScaleCurves();
+ AnimationClip::FloatCurves& floats = clip.GetFloatCurves();
+ CurveID curveID;
+
+ for (AnimationClip::QuaternionCurves::iterator i=rot.begin();i != rot.end();i++)
+ {
+ AnimationClip::QuaternionCurve& element = *i;
+
+ curveID = CurveID (element.path.c_str(), ClassID(Transform), NULL, "m_LocalRotation", element.hash);
+ if (element.hash == 0)
+ {
+ curveID.CalculateHash();
+ element.hash = curveID.hash;
+ }
+
+ AnimationBinder::InsertCurveIDIntoLookup(curveIDLookup, curveID);
+ }
+
+ for (AnimationClip::Vector3Curves::iterator i=pos.begin();i != pos.end();i++)
+ {
+ AnimationClip::Vector3Curve& element = *i;
+
+ curveID = CurveID (element.path.c_str(), ClassID(Transform), NULL, "m_LocalPosition", element.hash);
+ if (element.hash == 0)
+ {
+ curveID.CalculateHash();
+ element.hash = curveID.hash;
+ }
+ AnimationBinder::InsertCurveIDIntoLookup(curveIDLookup, curveID);
+ }
+
+ for (AnimationClip::Vector3Curves::iterator i=scale.begin();i != scale.end();i++)
+ {
+ AnimationClip::Vector3Curve& element = *i;
+
+ curveID = CurveID (element.path.c_str(), ClassID(Transform), NULL, "m_LocalScale", element.hash);
+ if (element.hash == 0)
+ {
+ curveID.CalculateHash();
+ element.hash = curveID.hash;
+ }
+
+
+ AnimationBinder::InsertCurveIDIntoLookup(curveIDLookup, curveID);
+ }
+
+ for (AnimationClip::FloatCurves::iterator i=floats.begin();i != floats.end();i++)
+ {
+ AnimationClip::FloatCurve& element = *i;
+ curveID = CurveID (element.path.c_str(), element.classID, element.script, element.attribute.c_str(), element.hash);
+ if (element.hash == 0)
+ {
+ curveID.CalculateHash();
+ element.hash = curveID.hash;
+ }
+
+ AnimationBinder::InsertCurveIDIntoLookup(curveIDLookup, curveID);
+ }
+}
+
+static bool IsMixedIn (const Animation::BoundCurves& boundCurves, int index, AnimationState& state)
+{
+ // We only mix transform animation
+ if (boundCurves[index].targetType == kBindTransformRotation || boundCurves[index].targetType == kBindTransformPosition || boundCurves[index].targetType == kBindTransformScale)
+ {
+ DebugAssertIf(dynamic_pptr_cast<Transform*> (boundCurves[index].targetObject) == NULL);
+ Transform* transform = static_cast<Transform*> (boundCurves[index].targetObject);
+
+ return state.ShouldMixTransform(*transform);
+ }
+ return true;
+}
+
+
+static void AssignBoundCurve (const AnimationBinder::CurveIDLookup& curveIDLookup, const CurveID& curveID, AnimationCurveBase* curve, const Animation::BoundCurves& boundCurves, AnimationState& state)
+{
+ AnimationBinder::CurveIDLookup::const_iterator found;
+ found = curveIDLookup.find(curveID);
+ if (found == curveIDLookup.end())
+ return;
+
+ if (!IsMixedIn (boundCurves, found->second, state))
+ return;
+
+ AnimationState::Curves curves = state.GetCurves();
+ curves[found->second] = curve;
+}
+
+static void CalculateAnimationClipCurves (const AnimationBinder::CurveIDLookup& curveIDLookup, AnimationClip& clip, const Animation::BoundCurves& boundCurves, AnimationState& state)
+{
+ AnimationClip::QuaternionCurves& rot = clip.GetRotationCurves();
+ AnimationClip::Vector3Curves& pos = clip.GetPositionCurves();
+ AnimationClip::Vector3Curves& scale = clip.GetScaleCurves();
+ AnimationClip::FloatCurves& floats = clip.GetFloatCurves();
+
+ CurveID curveID;
+
+ for (AnimationClip::QuaternionCurves::iterator i=rot.begin();i != rot.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+ AssertIf(i->hash == 0);
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalRotation", i->hash);
+ AssignBoundCurve(curveIDLookup, curveID, (AnimationCurveBase*)&i->curve, boundCurves, state);
+ }
+
+ for (AnimationClip::Vector3Curves::iterator i=pos.begin();i != pos.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ AssertIf(i->hash == 0);
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalPosition", i->hash);
+ AssignBoundCurve(curveIDLookup, curveID, (AnimationCurveBase*)&i->curve, boundCurves, state);
+ }
+
+ for (AnimationClip::Vector3Curves::iterator i=scale.begin();i != scale.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ AssertIf(i->hash == 0);
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalScale", i->hash);
+ AssignBoundCurve(curveIDLookup, curveID, (AnimationCurveBase*)&i->curve, boundCurves, state);
+ }
+
+ for (AnimationClip::FloatCurves::iterator i=floats.begin();i != floats.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ AssertIf(i->hash == 0);
+ curveID = CurveID (i->path.c_str(), i->classID, i->script, i->attribute.c_str(), i->hash);
+ AssignBoundCurve(curveIDLookup, curveID, (AnimationCurveBase*)&i->curve, boundCurves, state);
+ }
+}
+
+void Animation::ValidateBoundCurves ()
+{
+ PROFILER_AUTO(gValidate, this);
+ BoundCurves::const_iterator end = m_BoundCurves.end();
+ for (BoundCurves::const_iterator i=m_BoundCurves.begin(); i != end; ++i)
+ {
+ const BoundCurveDeprecated& bound = *i;
+
+#if 0
+ // We are accessing potentially deleted memory and validating it if the instanceID matches.
+ // This is unsafe, but will work in almost all cases. ValidateBoundCurves does not trigger in normal circumstances.
+ // Yet we have to pay a large cost for the safety provided... This seems not fair.
+ if (bound.targetObject->GetInstanceID () != bound.targetInstanceID)
+#else
+ if (Object::IDToPointer(bound.targetInstanceID) != bound.targetObject)
+#endif
+ {
+ PROFILER_AUTO(gDidDestroyObjectNotification, this);
+ CleanupBoundCurves ();
+ return;
+ }
+ }
+}
+
+void Animation::CleanupBoundCurves ()
+{
+ if (m_BoundCurves.empty ())
+ {
+ return;
+ }
+
+ m_BoundCurves.clear();
+ m_DirtyMask |= kRebindDirtyMask;
+}
+
+
+void Animation::RebuildStateForEverything ()
+{
+ PROFILER_AUTO(gBuildAnimationState, this)
+
+ //@TODO: we should make sure that the curves are aligned for better cache utilization
+ // -> Simply modify curveIDLookup::iterator->second to be in the right cache order, after building the curveIDLookup!
+ // Possibly caching is already pretty good if the first curve contains all curves!
+
+ AnimationBinder::CurveIDLookup curveIDLookup;
+ AnimationBinder::InitCurveIDLookup(curveIDLookup);
+
+ /// Build curveIDLookup
+ /// * walk through all animation states and insert the curve into curveIDLookup
+ /// * curveCount gets increased when we encounter a new curve
+ CurveID curveID ("", 0, NULL, "", 0);
+ AnimationState* state;
+ AnimationBinder::CurveIDLookup::iterator found;
+
+ string attribute;
+
+ Transform* transform = QueryComponent (Transform);
+
+ if (transform)
+ {
+ for (int s=0;s<m_AnimationStates.size();s++)
+ {
+ state = m_AnimationStates[s];
+ AnimationClip* clip = state->m_Clip;
+ if (clip == NULL)
+ continue;
+
+ InsertAnimationClipCurveIDs(curveIDLookup, *clip);
+ }
+
+ // Bind the global curves.
+ GetAnimationBinder().BindCurves(curveIDLookup, *transform, m_BoundCurves, m_CachedAffectedSendToRootTransform, m_CachedTransformMessageMask);
+
+ // This will compact any curves that couldn't be bound
+ // - Faster at runtime (Can remove some ifs)
+ // - Slower at load time
+ AnimationBinder::RemoveUnboundCurves(curveIDLookup, m_BoundCurves);
+
+
+ /// * Build state.m_Curves arrays
+ /// - We go through all curves in the every state/clip look it up in the curve map
+ for (int s=0;s<m_AnimationStates.size();s++)
+ {
+ state = m_AnimationStates[s];
+ // Initialize all states to have no curves assigned
+ // Those that aren't used or excluded for mixing will remain null after this loop
+ state->CleanupCurves();
+ state->AllocateCurves(curveIDLookup.size());
+
+ AnimationClip* clip = state->m_Clip;
+ if (clip == NULL)
+ continue;
+
+ CalculateAnimationClipCurves(curveIDLookup, *clip, m_BoundCurves, *state);
+ }
+ }
+
+ // Force Bound curves mask recalculation
+ m_ActiveAnimationStatesSize = 0;
+
+ m_DirtyMask &= ~kRebindDirtyMask;
+}
+
+inline float InverseWeight(float weight)
+{
+ return weight > kReallySmallWeight ? (1.0F / weight) : 0.0F;
+}
+
+/*** This calculates the blend weights for one curve.
+
+Weights are distributed so that the top layer gets everything.
+If it doesn't use the full weight then the next layer gets to distribute the remaining
+weights and so on. Once all weights are used by the top layers,
+no weights will be available for lower layers anymore
+We use fair weighting, which means if a lower layer wants 80% and 50% have already been used up, the layer will NOT use up all weights.
+instead it will take up 80% of the 50%.
+
+Example:
+a upper body which is affected by wave, walk and idle
+a lower body which is affected by only walk and idle
+
+weight name layer lower upper
+ 20% wave 2 0% 20%
+ 50% walk 1 50% 40%
+ 100% idle 0 50% 40%
+
+- Blend weights can change per animated value because of mixing.
+ Even without mixing, sometimes a curve is just not defined. Still you want the blend weights to add up to 1.
+ Most of the time weights are similar between curves. So there is a lot of caching one can do.
+*/
+
+// We use fair weighting, which means if a lower layer wants 80% and 50% have already been used up, the layer will NOT use up all weights.
+// Instead it will take up 80% of the 50%.
+#define FAIR_DISTRIBUTION 1
+
+template<bool optimize32States>
+void CalculateWeights( AnimationState** states, int stateCount, int curveIndex, OUTPUT_OPTIONAL float* outWeights, int mask )
+{
+ Assert( outWeights != NULL );
+
+ // state index -> layer index
+ int* layerIndices;
+ ALLOC_TEMP(layerIndices, int, stateCount);
+ // summed weights for each layer - we'll never have more layers than affectors
+ float* layerSummedWeights;
+ ALLOC_TEMP(layerSummedWeights, float, stateCount);
+
+ const AnimationState* state = states[0];
+ int prevLayer = state->GetLayer();
+ int layerIndex = 0;
+
+ // Clear summed layer weights
+ for( int i = 0; i < stateCount; ++i )
+ layerSummedWeights[i] = 0.0F;
+
+ // sum weights for each layer
+ UInt32 stateBit = 1;
+ for( int i = 0; i < stateCount; ++i )
+ {
+ if (optimize32States)
+ {
+ if (mask & stateBit)
+ {
+ state = states[i];
+
+ AssertIf( prevLayer < state->GetLayer()); // Algorithm requires sorted states by layer
+
+ if( prevLayer != state->GetLayer() )
+ ++layerIndex;
+
+ layerSummedWeights[layerIndex] += state->GetWeight();
+ layerIndices[i] = layerIndex;
+ outWeights[i] = state->GetWeight();
+ }
+ else
+ {
+ outWeights[i] = 0.0F;
+ layerIndices[i] = 0;
+ }
+ }
+ else
+ {
+ state = states[i];
+ if (state->ShouldUse() && state->GetCurves()[curveIndex] != NULL && state->GetBlendMode() == AnimationState::kBlend)
+ {
+ AssertIf( prevLayer < state->GetLayer()); // Algorithm requires sorted states by layer
+
+ if( prevLayer != state->GetLayer() )
+ ++layerIndex;
+
+ layerSummedWeights[layerIndex] += state->GetWeight();
+ layerIndices[i] = layerIndex;
+ outWeights[i] = state->GetWeight();
+ }
+ else
+ {
+ outWeights[i] = 0.0F;
+ layerIndices[i] = 0;
+ }
+ }
+ stateBit <<= 1;
+ prevLayer = states[i]->GetLayer();
+ }
+ int layerCount = layerIndex + 1;
+
+ // Distribute weights so that the top layers get everything up to 1.
+ // If they use less, the remainder goes to the lower layers.
+ float* layerInvSummedWeights;
+ ALLOC_TEMP(layerInvSummedWeights, float, stateCount);
+ float remainderWeight = 1.0F;
+ for( int i = 0; i < layerCount; ++i )
+ {
+ float layerWeight = max(1.0F, layerSummedWeights[i]);
+
+ layerInvSummedWeights[i] = InverseWeight(layerWeight) * remainderWeight;
+
+ #if FAIR_DISTRIBUTION
+ remainderWeight -= layerSummedWeights[i] * remainderWeight;
+ #else
+ remainderWeight -= layerSummedWeights[i];
+ #endif
+
+ remainderWeight = max(0.0F, remainderWeight);
+ }
+
+ // - Apply the layer inverse weights
+ // - Normalize the weights once again, just in case
+
+ // @TODO: Renormalization is only necessary if the remainderWeight is larger than zero as far as i can see
+ float summedWeight = 0.0F;
+ for( int i = 0; i < stateCount; ++i )
+ {
+ outWeights[i] *= layerInvSummedWeights[layerIndices[i]];
+ summedWeight += outWeights[i];
+ }
+
+ summedWeight = InverseWeight(summedWeight);
+ for( int i = 0; i < stateCount; ++i )
+ outWeights[i] *= summedWeight;
+}
+
+void Animation::SampleDefaultClip (double time)
+{
+ AnimationClip* clip = m_Animation;
+ if (!clip)
+ return;
+
+ SampleAnimation(GetGameObject(), *clip, time, CombineWrapMode(clip->GetWrapMode(), m_WrapMode));
+}
+
+// Calculates a bitmask for every bound curve, containing which curve state affects it.
+// - The recalculation is only necessary if a state got enabled or disabled (Either through manually disabling it or through a too low blend weight)
+bool Animation::RebuildBoundStateMask()
+{
+ int activeStateCount = 0;
+ bool requireRebuild = false;
+ int s;
+ for (s=0;s<m_AnimationStates.size() && activeStateCount < 32;s++)
+ {
+ AnimationState& state = *m_AnimationStates[s];
+ if (state.ShouldUse() && state.GetBlendMode() == AnimationState::kBlend)
+ {
+ requireRebuild |= m_ActiveAnimationStates[activeStateCount] != &state;
+ m_ActiveAnimationStates[activeStateCount] = &state;
+ DebugAssertIf(activeStateCount != 0 && m_ActiveAnimationStates[activeStateCount-1]->GetLayer() < m_ActiveAnimationStates[activeStateCount]->GetLayer());
+ activeStateCount++;
+ }
+ }
+
+ // Too many active animation states
+ if (s != m_AnimationStates.size())
+ return false;
+
+ requireRebuild |= activeStateCount != m_ActiveAnimationStatesSize;
+ // early out if nothing in which animations are currently playing has changed.
+ if (!requireRebuild)
+ return true;
+
+ m_ActiveAnimationStatesSize = activeStateCount;
+
+ for (int i=0;i<m_BoundCurves.size();i++)
+ {
+ m_BoundCurves[i].affectedStateMask = 0;
+ for (int s=0;s<m_ActiveAnimationStatesSize;s++)
+ {
+ AnimationState& state = *m_ActiveAnimationStates[s];
+ if (state.m_Curves[i])
+ m_BoundCurves[i].affectedStateMask |= 1 << s;
+ }
+ }
+
+ return true;
+}
+
+inline void AwakeAndDirty (Object* o)
+{
+ if (o)
+ {
+ o->AwakeFromLoad(kDefaultAwakeFromLoad);
+ o->SetDirty();
+ }
+}
+
+static void UpdateLastNonTransformObject(Object*& lastNonTransformObject, Object* const targetObject)
+{
+ if (lastNonTransformObject != targetObject)
+ {
+ AwakeAndDirty(lastNonTransformObject);
+ lastNonTransformObject = targetObject;
+ }
+}
+
+// Optimized for 32 animation states
+void Animation::BlendOptimized()
+{
+ AssertIf(m_BoundCurves.empty());
+
+ ///@TODO: Keep a cache for all curves and pass it into animationcurve.Evaluate.
+ // So that many characters dont kill the cache of shared animation curves.
+
+ int curveCount = m_BoundCurves.size();
+
+ AnimationState** activeStates = m_ActiveAnimationStates;
+ int stateSize = m_ActiveAnimationStatesSize;
+ float* weights;
+ ALLOC_TEMP(weights, float, stateSize);
+
+ float weight;
+ const AnimationState* state;
+
+ Object* lastNonTransformObject = NULL;
+ UInt32 lastAffectedStateMask = m_BoundCurves[0].affectedStateMask;
+ CalculateWeights<true>(activeStates, stateSize, 0, weights, lastAffectedStateMask);
+ BoundCurveDeprecated* boundCurves = &m_BoundCurves[0];
+
+ for( int c = 0; c < curveCount; ++c )
+ {
+ BoundCurveDeprecated& bind = boundCurves[c];
+
+ // Only recalculate weights if the state mask changes!
+ // This happens very rarely. (If you dont use mixing it never happens)
+ if (lastAffectedStateMask != bind.affectedStateMask)
+ {
+ lastAffectedStateMask = bind.affectedStateMask;
+ CalculateWeights<true>(activeStates, stateSize, c, weights, lastAffectedStateMask);
+ }
+
+ if (lastAffectedStateMask == 0)
+ continue;
+
+ const UInt32 targetType = bind.targetType;
+
+ // Sample quaternion
+ if (targetType == kBindTransformRotation)
+ {
+ Prefetch(bind.targetPtr);
+ Quaternionf& result = *(Quaternionf*)bind.targetPtr;
+ result.Set(0,0,0,0);
+ UInt32 stateBit = 1;
+ for (int i=0;i<stateSize;i++)
+ {
+ if (lastAffectedStateMask & stateBit)
+ {
+ state = activeStates[i];
+ const AnimationCurveQuat* quatCurve = reinterpret_cast<AnimationCurveQuat*>(state->GetCurves()[c]);
+
+ Quaternionf sample = quatCurve->EvaluateClamp(state->m_WrappedTime);
+ DebugAssertIf(!IsFinite(sample));
+
+ /// Not necessary because we make sure when sampling that our curves are sampled and almost normalized
+ /// @todo: If people animate inside unity this might be a problem.
+ /// Hopefully no one does that in combination with blending.
+ // sample = NormalizeFastEpsilonZero(sample);
+ result += Sign(Dot (sample, result)) * sample * weights[i];
+ }
+ stateBit <<= 1;
+ }
+
+ result = NormalizeSafe(result);
+ DebugAssertIf(!IsFinite(result));
+ }
+ // Sample vector 3
+ else if (targetType == kBindTransformPosition)
+ {
+ Vector3f result = Vector3f(0.0F, 0.0F, 0.0F);
+ UInt32 stateBit = 1;
+ for (int i=0;i<stateSize;i++)
+ {
+ if (lastAffectedStateMask & stateBit)
+ {
+ state = activeStates[i];
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ }
+ stateBit <<= 1;
+ }
+
+ DebugAssertIf(!IsFinite(result));
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ }
+ else if (targetType == kBindTransformScale)
+ {
+ Vector3f result = Vector3f(0.0F, 0.0F, 0.0F);
+ UInt32 stateBit = 1;
+ for (int i=0;i<stateSize;i++)
+ {
+ if (lastAffectedStateMask & stateBit)
+ {
+ state = activeStates[i];
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ }
+ stateBit <<= 1;
+ }
+
+ DebugAssertIf(!IsFinite(result));
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ Transform* targetTransform = reinterpret_cast<Transform*> (bind.targetObject);
+ targetTransform->RecalculateTransformType ();
+ }
+ // Sample float
+ else if (targetType > kUnbound)
+ {
+ float result = 0.0F;
+ UInt32 stateBit = 1;
+ for (int i=0;i<stateSize;i++)
+ {
+ if (lastAffectedStateMask & stateBit)
+ {
+ state = activeStates[i];
+ const AnimationCurve* floatCurve = reinterpret_cast<AnimationCurve*>(state->GetCurves()[c]);
+
+ float sample = floatCurve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ }
+ stateBit <<= 1;
+ }
+
+ DebugAssertIf(!IsFinite(result));
+
+ AnimationBinder::SetFloatValue(bind, result);
+
+ if (AnimationBinder::ShouldAwakeGeneric (bind))
+ UpdateLastNonTransformObject(lastNonTransformObject, bind.targetObject);
+
+ }
+ else
+ {
+ #if COMPACT_UNBOUND_CURVES
+ AssertString("Unbound curves should be compacted!");
+ #endif
+ continue;
+ }
+
+ }
+
+ AwakeAndDirty(lastNonTransformObject);
+}
+
+
+// Unlimited amount of animation states
+void Animation::BlendGeneric()
+{
+ AssertIf(m_BoundCurves.empty());
+
+ ///@TODO: Keep a cache for all curves and pass it into animationcurve.Evaluate.
+ // So that many characters dont kill the cache of shared animation curves.
+
+ int curveCount = m_BoundCurves.size();
+
+ int stateSize = m_AnimationStates.size();
+ float* weights;
+ ALLOC_TEMP(weights, float, stateSize);
+
+ float weight;
+ const AnimationState* state;
+
+ Object* lastNonTransformObject = NULL;
+ for( int c = 0; c < curveCount; ++c )
+ {
+ BoundCurveDeprecated& bind = m_BoundCurves[c];
+
+ CalculateWeights<false>(&m_AnimationStates[0], stateSize, c, weights, 0);
+
+ bool didSample = false;
+
+ int targetType = bind.targetType;
+
+ // Sample quaternion
+ if (targetType == kBindTransformRotation)
+ {
+ Quaternionf result = Quaternionf(0.0F, 0.0F, 0.0F, 0.0F);
+
+ for (int i=0;i<stateSize;i++)
+ {
+ state = m_AnimationStates[i];
+ const AnimationCurveQuat* quatCurve = reinterpret_cast<AnimationCurveQuat*>(state->GetCurves()[c]);
+ if (quatCurve && weights[i] > kReallySmallWeight)
+ {
+ Quaternionf sample = quatCurve->EvaluateClamp(state->m_WrappedTime);
+
+ /// Not necessary because we make sure when sampling that our curves are sampled and almost normalized
+ /// @todo: If people animate inside unity this might be a problem.
+ /// Hopefully no one does that in combination with blending.
+
+ // sample = NormalizeFastEpsilonZero(sample);
+ weight = weights[i];
+ if (Dot (sample, result) < 0.0F)
+ weight = -weight;
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+
+ result = NormalizeSafe(result);
+ DebugAssertIf(!IsFinite(result));
+ if (didSample)
+ *reinterpret_cast<Quaternionf*>(bind.targetPtr) = result;
+ }
+ // Sample vector 3
+ else if (targetType == kBindTransformPosition)
+ {
+ Vector3f result = Vector3f(0.0F, 0.0F, 0.0F);
+ for (int i=0;i<stateSize;i++)
+ {
+ state = m_AnimationStates[i];
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+ if (vec3Curve && weights[i] > kReallySmallWeight)
+ {
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+
+ DebugAssertIf(!IsFinite(result));
+ if (didSample)
+ {
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ }
+ }
+ // Sample vector 3
+ else if (targetType == kBindTransformScale)
+ {
+ Vector3f result = Vector3f(0.0F, 0.0F, 0.0F);
+ for (int i=0;i<stateSize;i++)
+ {
+ state = m_AnimationStates[i];
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+ if (vec3Curve && weights[i] > kReallySmallWeight)
+ {
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+
+ DebugAssertIf(!IsFinite(result));
+
+ if (didSample)
+ {
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ Transform* targetTransform = reinterpret_cast<Transform*> (bind.targetObject);
+ targetTransform->RecalculateTransformType ();
+ }
+ }
+ // Sample float
+ else if (targetType > kUnbound)
+ {
+ float result = 0.0F;
+ for (int i=0;i<stateSize;i++)
+ {
+ state = m_AnimationStates[i];
+ const AnimationCurve* floatCurve = reinterpret_cast<AnimationCurve*>(state->GetCurves()[c]);
+ if (floatCurve && weights[i] > kReallySmallWeight)
+ {
+ float sample = floatCurve->EvaluateClamp(state->m_WrappedTime);
+ weight = weights[i];
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+
+ DebugAssertIf(!IsFinite(result));
+
+ if (didSample)
+ {
+ AnimationBinder::SetFloatValue(bind, result);
+
+ if (AnimationBinder::ShouldAwakeGeneric (bind))
+ UpdateLastNonTransformObject(lastNonTransformObject, bind.targetObject);
+ }
+ }
+ else
+ {
+ #if COMPACT_UNBOUND_CURVES
+ AssertString("Unbound curves should be compacted!");
+ #endif
+ continue;
+ }
+
+ }
+
+ AwakeAndDirty(lastNonTransformObject);
+}
+
+void Animation::BlendAdditive()
+{
+ AssertIf(m_BoundCurves.empty());
+ int stateSize = m_AnimationStates.size();
+ AnimationState* state;
+
+ AnimationState** activeStates;
+ ALLOC_TEMP(activeStates, AnimationState*, stateSize);
+ int activeStateSize = 0;
+
+ ///@TODO: We can also compare if the current time matches the first frame, then the animation has no effect!
+ for (int i=0;i<stateSize;i++)
+ {
+ state = m_AnimationStates[i];
+ if (state->GetBlendMode() == AnimationState::kAdditive && state->ShouldUse ())
+ {
+ activeStates[activeStateSize] = state;
+ activeStateSize++;
+ }
+ }
+ // early out if we have no additive animations playing
+ if (activeStateSize == 0)
+ return;
+
+ int curveCount = m_BoundCurves.size();
+ BoundCurveDeprecated* boundCurves = &m_BoundCurves[0];
+// float* weights;
+// ALLOC_TEMP(weights, float, stateSize, kMemAnimation);
+// float* time;
+// ALLOC_TEMP(time, float, stateSize, kMemAnimation);
+
+ float weight;
+
+ Object* lastNonTransformObject = NULL;
+ for( int c = 0; c < curveCount; ++c )
+ {
+ BoundCurveDeprecated& bind = boundCurves[c];
+
+ bool didSample = false;
+
+ int targetType = bind.targetType;
+
+ // Sample quaternion
+ if (targetType == kBindTransformRotation)
+ {
+ Quaternionf result = *reinterpret_cast<Quaternionf*>(bind.targetPtr);
+
+ for (int i=0;i<activeStateSize;i++)
+ {
+ state = activeStates[i];
+ weight = clamp01 (state->GetWeight());
+ const AnimationCurveQuat* quatCurve = reinterpret_cast<AnimationCurveQuat*>(state->GetCurves()[c]);
+ if (quatCurve)
+ {
+ Quaternionf sample = Inverse(quatCurve->GetKey(0).value) * quatCurve->EvaluateClamp(state->m_WrappedTime);
+ sample = Lerp(Quaternionf::identity(), sample, weight);
+ result *= sample;
+ didSample = true;
+ }
+ }
+
+ result = NormalizeSafe(result);
+ DebugAssertIf(!IsFinite(result));
+ if (didSample)
+ *reinterpret_cast<Quaternionf*>(bind.targetPtr) = result;
+ }
+ // Sample vector 3
+ else if (targetType == kBindTransformPosition)
+ {
+ Vector3f result = *reinterpret_cast<Vector3f*>(bind.targetPtr);
+
+ for (int i=0;i<activeStateSize;i++)
+ {
+ state = activeStates[i];
+ weight = clamp01 (state->GetWeight());
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+
+ if (vec3Curve)
+ {
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime) - vec3Curve->GetKey(0).value;
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+ DebugAssertIf(!IsFinite(result));
+ if (didSample)
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ }
+ // Sample vector 3
+ else if (targetType == kBindTransformScale)
+ {
+ Vector3f result = *reinterpret_cast<Vector3f*>(bind.targetPtr);
+
+ for (int i=0;i<activeStateSize;i++)
+ {
+ state = activeStates[i];
+ weight = clamp01 (state->GetWeight());
+ const AnimationCurveVec3* vec3Curve = reinterpret_cast<AnimationCurveVec3*>(state->GetCurves()[c]);
+
+ if (vec3Curve)
+ {
+ Vector3f sample = vec3Curve->EvaluateClamp(state->m_WrappedTime) - vec3Curve->GetKey(0).value;
+ result += sample * weight;
+ didSample = true;
+ }
+ }
+ DebugAssertIf(!IsFinite(result));
+ if (didSample)
+ {
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ Transform* targetTransform = reinterpret_cast<Transform*> (bind.targetObject);
+ targetTransform->RecalculateTransformType ();
+ }
+ }
+ }
+
+ AwakeAndDirty(lastNonTransformObject);
+}
+
+inline Animation::CullingType RemapDeprecatedCullingType (Animation::CullingType type)
+{
+ if (type == Animation::kDeprecatedCulling_BasedOnClipBounds || type == Animation::kDeprecatedCulling_BasedOnUserBounds)
+ return Animation::kCulling_BasedOnRenderers;
+ else
+ return type;
+}
+
+void Animation::SetCullingType(CullingType type)
+{
+ type = RemapDeprecatedCullingType(type);
+
+ CheckIsCullingBasedOnBoundsDeprecated ();
+
+ // Clearing culling related data
+ if (m_CullingType == kCulling_BasedOnRenderers)
+ {
+ ClearContainedRenderers();
+ }
+ else if (m_CullingType == kCulling_AlwaysAnimate)
+ RemoveFromManager();
+
+ m_CullingType = type;
+
+ // Building new culling data
+ if (m_CullingType == kCulling_BasedOnRenderers && !m_AnimationStates.empty())
+ RecomputeContainedRenderers ();
+ else if (m_CullingType == kCulling_AlwaysAnimate && !m_AnimationManagerNode.IsInList())
+ AddToManager();
+
+ SetDirty();
+}
+
+void Animation::RemoveContainedRenderer (Renderer* renderer)
+{
+ ContainedRenderers::iterator end = m_ContainedRenderers.end();
+ for (ContainedRenderers::iterator i = m_ContainedRenderers.begin();i != end;++i)
+ {
+ Renderer* cur = *i;
+ if (cur == renderer)
+ {
+ *i = m_ContainedRenderers.back();
+ m_ContainedRenderers.resize(m_ContainedRenderers.size() - 1);
+ return;
+ }
+ }
+}
+
+static void AnimationVisibilityCallback (void* userData, void* senderUserData, int visibilityEvent)
+{
+ Animation& animation = *reinterpret_cast<Animation*> (userData);
+
+ if (visibilityEvent == kBecameVisibleEvent)
+ animation.SetVisibleRenderers(true);
+ else if (visibilityEvent == kBecameInvisibleEvent)
+ animation.CheckRendererVisibleState ();
+ else if (visibilityEvent == kWillDestroyEvent)
+ {
+ animation.RemoveContainedRenderer(reinterpret_cast<Renderer*> (senderUserData));
+
+ animation.CheckRendererVisibleState ();
+ }
+}
+
+void Animation::ClearContainedRenderers ()
+{
+ ContainedRenderers::iterator end = m_ContainedRenderers.end();
+ for (ContainedRenderers::iterator i = m_ContainedRenderers.begin();i != end;++i)
+ {
+ Renderer* renderer = *i;
+ renderer->RemoveEvent(AnimationVisibilityCallback, this);
+ }
+ m_ContainedRenderers.clear();
+}
+
+void Animation::RecomputeContainedRenderers ()
+{
+ Assert(m_CullingType == kCulling_BasedOnRenderers);
+
+ ClearContainedRenderers ();
+
+ Transform& transform = GetComponent (Transform);
+ RecomputeContainedRenderersRecurse(transform);
+
+ Assert(m_CullingType == kCulling_BasedOnRenderers);
+
+ CheckRendererVisibleState ();
+}
+
+void Animation::CheckRendererVisibleState ()
+{
+ Assert(m_CullingType == kCulling_BasedOnRenderers);
+
+ ContainedRenderers::iterator end = m_ContainedRenderers.end();
+ for (ContainedRenderers::iterator i = m_ContainedRenderers.begin();i != end;++i)
+ {
+ Renderer* renderer = *i;
+ Assert(renderer->HasEvent(AnimationVisibilityCallback, this));
+ if (renderer->IsVisibleInScene())
+ {
+ SetVisibleRenderers(true);
+ return;
+ }
+ }
+
+ SetVisibleRenderers(false);
+}
+
+void Animation::SetVisibleInternal(bool visible)
+{
+ Assert(m_CullingType != kCulling_AlwaysAnimate);
+ m_Visible = visible;
+
+ if (IsWorldPlaying())
+ {
+ const bool wasAttached = m_AnimationManagerNode.IsInList();
+ // Method is called AddToManager, but it does removal too...
+ AddToManager();
+
+ // Culling is after AnimationManager Update,
+ // so when we pop into visibility we should make sure we are rendering the right frame!
+ if (m_AnimationManagerNode.IsInList() && wasAttached == false)
+ UpdateAnimation(GetCurTime());
+ }
+}
+
+
+void Animation::SetVisibleRenderers(bool visible)
+{
+ //LogString(Format("SetVisibleRenderers %d %s", visible ? 1 : 0, GetGameObject().GetName()));
+
+ Assert(m_CullingType == kCulling_BasedOnRenderers);
+ SetVisibleInternal(visible);
+}
+
+void Animation::SetVisibleBounds(bool visible)
+{
+ //LogString(Format("SetVisibleBounds %d %s", visible ? 1 : 0, GetGameObject().GetName()));
+
+ CheckIsCullingBasedOnBoundsDeprecated ();
+ SetVisibleInternal(visible);
+}
+
+///@TODO: We must ensure that there are not two animation components in a hierarchy!!!. Otherwise SetAnimationPtr will break!
+
+void Animation::RecomputeContainedRenderersRecurse (Transform& transform)
+{
+ Renderer* renderer = transform.QueryComponent(Renderer);
+ if (renderer)
+ {
+ m_ContainedRenderers.push_back(renderer);
+ renderer->AddEvent(AnimationVisibilityCallback, this);
+ }
+ Transform::iterator end = transform.end();
+ for (Transform::iterator i = transform.begin();i != end;++i)
+ {
+ RecomputeContainedRenderersRecurse(**i);
+ }
+}
+
+void Animation::SendTransformChangedToCachedTransform()
+{
+ int physicsMask = m_AnimatePhysics ? Transform::kAnimatePhysics : 0;
+ int size = m_CachedAffectedSendToRootTransform.size();
+ for (int i=0;i<size;i++)
+ {
+ m_CachedAffectedSendToRootTransform[i]->SendTransformChanged(m_CachedTransformMessageMask | physicsMask);
+ }
+}
+
+void Animation::SyncLayerTime (int layer)
+{
+ float normalizedSpeed = 0.0F;
+ float normalizedTime = 0.0F;
+ float summedLayerWeight = 0.0F;
+
+ for (AnimationStates::iterator i=m_AnimationStates.begin();i!=m_AnimationStates.end();i++)
+ {
+ AnimationState& state = **i;
+ if (state.GetLayer() != layer || !state.GetEnabled())
+ continue;
+
+ float weight = max(state.GetWeight(), 0.0F);
+ normalizedSpeed += state.GetNormalizedSpeed() * weight;
+ normalizedTime += state.GetNormalizedTime() * weight;
+ summedLayerWeight += weight;
+ }
+
+ if (summedLayerWeight > kReallySmallWeight)
+ {
+ normalizedSpeed = normalizedSpeed / summedLayerWeight;
+ normalizedTime = normalizedTime / summedLayerWeight;
+
+ for (AnimationStates::iterator i=m_AnimationStates.begin();i!=m_AnimationStates.end();i++)
+ {
+ AnimationState& state = **i;
+ if (state.GetLayer() != layer || !state.GetEnabled())
+ continue;
+
+ state.SetNormalizedSyncedSpeed(normalizedSpeed);
+ state.SetNormalizedTime(normalizedTime);
+ }
+ }
+}
+
+void Animation::Sample ()
+{
+ bool needsUpdate = false;
+
+ // Update animation state
+ for (int i=0;i<m_AnimationStates.size();i++)
+ {
+ AnimationState& state = *m_AnimationStates[i];
+
+ // Do we actually use any of the animation states?
+ if (state.ShouldUse())
+ needsUpdate = true;
+
+ m_DirtyMask |= state.GetDirtyMask();
+ state.ClearDirtyMask();
+ }
+
+ if (needsUpdate)
+ SampleInternal();
+}
+
+void Animation::SampleInternal()
+{
+ PROFILER_AUTO(gSampleAnimation, this)
+
+ ValidateBoundCurves ();
+
+ if (m_DirtyMask != 0)
+ {
+ if (m_DirtyMask & kRebindDirtyMask)
+ RebuildStateForEverything();
+
+ if (m_DirtyMask & kLayersDirtyMask)
+ SortAnimationStates();
+ }
+
+ AssertIf(m_DirtyMask != 0);
+
+ if (!m_BoundCurves.empty())
+ {
+ if (RebuildBoundStateMask())
+ {
+ if (m_ActiveAnimationStatesSize != 0)
+ BlendOptimized();
+ }
+ else
+ {
+ /// More than 32 animation states active at the same time!
+ /// @TODO: 3.0 take this out. Possibly sort animation states by maximum weight.
+ /// Or maybe supporting more than 32 states at once is really pointless
+ BlendGeneric();
+ }
+
+ BlendAdditive();
+
+ SendTransformChangedToCachedTransform();
+ }
+}
+
+void Animation::UpdateAnimation (double time)
+{
+ if (AnimationState::UseUnity32AnimationFixes())
+ UpdateAnimationInternal(time);
+ else
+ UpdateAnimationInternal_Before32(time);
+}
+
+// Calculates remaining play-times for all animations and for specified layer
+static void GetQueueTimes(const Animation::AnimationStates& states, const int targetLayer, float& allQueueTime, float& layerQueueTime)
+{
+ allQueueTime = 0;
+ layerQueueTime = 0;
+
+ for (Animation::AnimationStates::const_iterator it = states.begin(), end = states.end(); it != end; ++it)
+ {
+ const AnimationState& state = **it;
+
+ if (state.GetEnabled())
+ {
+ const int layer = state.GetLayer();
+
+ const int wrapMode = state.GetWrapMode();
+ if (wrapMode != kDefaultWrapMode && wrapMode != kClamp)
+ {
+ // for "infinite" animations (Loop, Repeat, ClampForever) we mark layer as "occupied"
+ allQueueTime = std::numeric_limits<float>::infinity();
+ if (layer == targetLayer)
+ layerQueueTime = std::numeric_limits<float>::infinity();
+ }
+ else
+ {
+ const float dt = state.GetLength() - state.GetTime();
+ Assert(dt >= 0);
+
+ allQueueTime = std::max(allQueueTime, dt);
+ if (layer == targetLayer)
+ layerQueueTime = std::max(layerQueueTime, dt);
+ }
+ }
+ }
+}
+
+// This function starts Queued animations (if it's already time to start).
+// We always blend animations based on QueuedAnimation::fadeTime, i.e. it doesn't
+// matter if currently playing animation(s) will finish in shorter time we will blend
+// in the new one in QueuedAnimation::fadeTime. We leave up to a user to specify
+// sufficient blend times.
+void Animation::UpdateQueuedAnimations(bool& needsUpdate)
+{
+ int lastLayer = -1;
+ float allQueueTime, lastLayerQueueTime;
+ allQueueTime = lastLayerQueueTime = -1;
+
+ // Update queued animations
+ for (QueuedAnimations::iterator q = m_Queued.begin(); q != m_Queued.end(); )
+ {
+ const QueuedAnimation& qa = *q;
+ const float fadeTime = qa.fadeTime;
+
+ const int layer = qa.state->GetLayer();
+
+ bool startNow = false;
+ if (qa.mode == kStopAll)
+ {
+ // queuing after all animations
+
+ if (allQueueTime < 0)
+ {
+ // allQueueTime must to be recalculated
+ GetQueueTimes(m_AnimationStates, layer, allQueueTime, lastLayerQueueTime);
+ lastLayer = layer;
+ }
+
+ startNow = fadeTime >= allQueueTime;
+ }
+ else
+ {
+ // queuing after animations in specific layer
+
+ if (lastLayer != layer || lastLayerQueueTime < 0)
+ {
+ // lastLayerQueueTime must to be recalculated
+ GetQueueTimes(m_AnimationStates, layer, allQueueTime, lastLayerQueueTime);
+ lastLayer = layer;
+ }
+
+ startNow = fadeTime >= lastLayerQueueTime;
+ }
+
+ if (startNow)
+ {
+ // This crossfade logic is framerate specific. We know when this animation had
+ // to be started, so in theory we should advance time and blending value (and execute events),
+ // but we don't do that because it would be an over-complication.
+ AnimationState& state = *qa.state;
+
+ CrossFade(state, fadeTime, qa.mode, false);
+ q = m_Queued.erase(q);
+ needsUpdate = true;
+
+ Assert(state.GetEnabled());
+ // we need to recalculate queue times, because we just started an animation
+ allQueueTime = lastLayerQueueTime = -1;
+ }
+ else
+ ++q;
+ }
+}
+
+void Animation::UpdateQueuedAnimations_Before34(bool& needsUpdate)
+{
+ // Update queued animations
+ for (QueuedAnimations::iterator q = m_Queued.begin(); q != m_Queued.end(); )
+ {
+ const QueuedAnimation& qa = *q;
+ if ((qa.mode == kStopAll && !IsPlaying()) || (qa.mode != kStopAll && !IsPlayingLayer(qa.state->GetLayer())))
+ {
+ CrossFade(*qa.state, qa.fadeTime, qa.mode, false);
+ q = m_Queued.erase(q);
+ needsUpdate = true;
+ }
+ else
+ ++q;
+ }
+}
+
+void Animation::UpdateAnimationInternal(double time)
+{
+ PROFILER_AUTO(gUpdateAnimation, this)
+
+ bool needsUpdate = false;
+
+ // Sync animations
+ for (SyncedLayers::iterator sync=m_SyncedLayers.begin();sync != m_SyncedLayers.end();sync++)
+ SyncLayerTime(*sync);
+
+ int stoppedAnimationCount = 0;
+ AnimationState** stoppedAnimations = NULL;
+ ALLOC_TEMP(stoppedAnimations, AnimationState*, m_AnimationStates.size());
+
+ // Update animation state
+ for (int i=0;i<m_AnimationStates.size();)
+ {
+ AnimationState& state = *m_AnimationStates[i];
+
+ // Update state
+ if (state.GetEnabled())
+ {
+ if (state.UpdateAnimationState(time, *this))
+ {
+ if (!state.ShouldAutoCleanupNow())
+ stoppedAnimations[stoppedAnimationCount++] = &state;
+ }
+ }
+
+ // Do we actually use any of the animation states?
+ if (state.ShouldUse())
+ needsUpdate = true;
+
+ m_DirtyMask |= state.GetDirtyMask();
+ state.ClearDirtyMask();
+
+ // Cleanup queued animations that have finished playing
+ if (state.ShouldAutoCleanupNow())
+ {
+ delete &state;
+ m_DirtyMask |= kLayersDirtyMask;
+ m_AnimationStates.erase(m_AnimationStates.begin() + i);
+ m_ActiveAnimationStatesSize = 0;
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ if (AnimationState::UseUnity34AnimationFixes())
+ UpdateQueuedAnimations(needsUpdate);
+ else
+ UpdateQueuedAnimations_Before34(needsUpdate);
+
+ if (stoppedAnimationCount > 0)
+ {
+ for (int i = 0; i < stoppedAnimationCount; ++i)
+ stoppedAnimations[i]->SetupUnstoppedState();
+
+ needsUpdate = true;
+ }
+
+ // Only do blending if it is really necessary
+ if (needsUpdate)
+ {
+ SampleInternal();
+ }
+
+ for (int i = 0; i < stoppedAnimationCount; ++i)
+ stoppedAnimations[i]->CleanupUnstoppedState();
+}
+
+// This is for backwards compatibility. If you need to make changes,
+// then make them in UpdateAnimationInternal which is used with Unity 3.2 and later content
+void Animation::UpdateAnimationInternal_Before32(double time)
+{
+ PROFILER_AUTO(gUpdateAnimation, this)
+
+ bool needsUpdate = false;
+ int activeLastStateCount = 0;
+
+ // Sync animations
+ for (SyncedLayers::iterator sync=m_SyncedLayers.begin();sync != m_SyncedLayers.end();sync++)
+ SyncLayerTime(*sync);
+
+ AnimationState* stoppedAnimation = NULL;
+
+ // Update animation state
+ for (int i=0;i<m_AnimationStates.size();)
+ {
+ AnimationState& state = *m_AnimationStates[i];
+
+ if (state.ShouldUse())
+ activeLastStateCount++;
+
+ // Update state
+ if (state.GetEnabled())
+ {
+ if (state.UpdateAnimationState(time, *this))
+ {
+ if (!state.ShouldAutoCleanupNow())
+ stoppedAnimation = &state;
+ }
+ }
+
+ // Do we actually use any of the animation states?
+ if (state.ShouldUse())
+ needsUpdate = true;
+
+ m_DirtyMask |= state.GetDirtyMask();
+ state.ClearDirtyMask();
+
+ // Cleanup queued animations that have finished playing
+ if (state.ShouldAutoCleanupNow())
+ {
+ delete &state;
+ m_DirtyMask |= kLayersDirtyMask;
+ m_AnimationStates.erase(m_AnimationStates.begin() + i);
+ m_ActiveAnimationStatesSize = 0;
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ UpdateQueuedAnimations_Before34(needsUpdate);
+
+ bool revertWrappedTimeToBeforeStop = false;
+ if (activeLastStateCount == 1 && needsUpdate == false && stoppedAnimation)
+ {
+ stoppedAnimation->SetupUnstoppedState();
+ revertWrappedTimeToBeforeStop = true;
+ }
+
+ // Only do blending if it is really necessary
+ if (needsUpdate)
+ {
+ SampleInternal();
+ }
+
+ if (revertWrappedTimeToBeforeStop)
+ stoppedAnimation->CleanupUnstoppedState();
+}
+
+
+struct GreaterLayer : std::binary_function<AnimationState*, AnimationState*, std::size_t>
+{
+ bool operator () (AnimationState* lhs, AnimationState* rhs) const
+ {
+ if (lhs->GetLayer() != rhs->GetLayer())
+ return lhs->GetLayer() > rhs->GetLayer();
+ else
+ return lhs->GetName() > rhs->GetName();
+ }
+};
+
+void Animation::SortAnimationStates ()
+{
+ sort(m_AnimationStates.begin(), m_AnimationStates.end(), GreaterLayer());
+ m_DirtyMask &= ~kLayersDirtyMask;
+ m_ActiveAnimationStatesSize = 0;
+}
+
+void Animation::ReleaseAnimationStates ()
+{
+ for (AnimationStates::iterator i=m_AnimationStates.begin();i!=m_AnimationStates.end();i++)
+ {
+ delete *i;
+ }
+ m_AnimationStates.clear();
+}
+
+AnimationClip* Animation::GetClipLegacyWarning (AnimationClip* clip)
+{
+ if (clip == NULL)
+ return NULL;
+ else
+ {
+ if (clip->GetAnimationType () == AnimationClip::kLegacy)
+ return clip;
+ else
+ {
+ WarningStringObject(Format("The AnimationClip '%s' used by the Animation component '%s' must be marked as Legacy.", clip->GetName(), GetName()), clip);
+ return NULL;
+ }
+ }
+}
+
+
+void Animation::BuildAnimationStates()
+{
+ if (!m_AnimationStates.empty())
+ return;
+ if (m_Animations.empty ())
+ return;
+
+ PROFILER_AUTO(gBuildAnimationState, this)
+
+ ReleaseAnimationStates();
+
+ m_AnimationStates.reserve(m_Animations.size());
+
+ double time = GetCurTime();
+ for (int i=0;i<m_Animations.size();i++)
+ {
+ AnimationClip* clip = GetClipLegacyWarning(m_Animations[i]);
+ if (clip != NULL)
+ {
+ m_AnimationStates.push_back(new AnimationState());
+ m_AnimationStates.back()->Init(clip->GetName(), clip, time, CombineWrapMode(clip->GetWrapMode(), m_WrapMode));
+ }
+ }
+
+ if (m_CullingType == kCulling_BasedOnRenderers)
+ RecomputeContainedRenderers();
+
+ m_DirtyMask |= kRebindDirtyMask;
+
+ AddToManager();
+}
+
+AnimationState* Animation::CloneAnimation (AnimationState* state)
+{
+ // The animation state needs to be attached to this animation
+ if (GetState(state) == NULL)
+ return NULL;
+
+ PROFILER_AUTO(gCloneAnimationState, this)
+
+ // Clone the state and reference all it's bound curves
+ AnimationState* clone = new AnimationState();
+ clone->Init( state->GetName() + " - Queued Clone", state->GetClip(), GetCurTime(), state->GetWrapMode(), true );
+ clone->SetParentName( state->GetName() );
+ clone->SetLayer(state->GetLayer());
+ clone->SetClonedCurves(*state);
+ clone->ClearDirtyMask();
+ m_AnimationStates.push_back(clone);
+
+ m_DirtyMask |= kLayersDirtyMask;
+ return clone;
+}
+
+void Animation::AddClip (AnimationClip& clip, const std::string& newName, int firstFrame, int lastFrame, bool loop)
+{
+ PROFILER_AUTO (gAddClip, this)
+
+ if (GetClipLegacyWarning (&clip) == NULL)
+ return;
+
+ AnimationClip* newClip = &clip;
+ // Do we really need to create a duplicate clip?
+ if (loop || firstFrame != INT_MIN || lastFrame != INT_MAX || newName != clip.GetName())
+ {
+ newClip = NEW_OBJECT (AnimationClip);
+
+ CopySerialized(clip, *newClip);
+ newClip->SetName(newName.c_str());
+
+ if (loop || firstFrame != INT_MIN || lastFrame != INT_MAX)
+ {
+ // [case 504486] need to clear curve because function ClipAnimation() will add all these clipped curves from source clip.
+ newClip->ClearCurves();
+ ClipAnimation(clip, *newClip, FrameToTime(firstFrame, clip.GetSampleRate()), FrameToTime(lastFrame, clip.GetSampleRate()), loop);
+ }
+ }
+
+
+ // Replace clips with duplicate names
+ Animations::iterator i;
+ for (i=m_Animations.begin();i != m_Animations.end();i++)
+ {
+ AnimationClip* cur = *i;
+ if (cur && cur->GetName() == newName)
+ break;
+ }
+ if (i == m_Animations.end())
+ m_Animations.push_back(newClip);
+ else
+ *i = newClip;
+
+ if (!m_AnimationStates.empty())
+ {
+ m_DirtyMask |= kRebindDirtyMask;
+
+ // Remove states with duplicate names
+ for (AnimationStates::iterator s=begin();s != end();s++)
+ {
+ if ((**s).GetName() == newName)
+ {
+ delete *s;
+ m_AnimationStates.erase(s);
+ break;
+ }
+ }
+
+ m_AnimationStates.push_back(new AnimationState());
+ m_AnimationStates.back()->Init(newName, newClip, GetCurTime(), CombineWrapMode(newClip->GetWrapMode(), m_WrapMode));
+ }
+
+ CheckIsCullingBasedOnBoundsDeprecated();
+
+ SetDirty();
+}
+
+void Animation::AddClip (AnimationClip& clip)
+{
+ AddClip(clip, clip.GetName(), std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), false);
+}
+
+void Animation::RemoveClip (AnimationClip& clip)
+{
+ PROFILER_AUTO(gRemoveClip, this)
+
+ // Find the clip to remove
+ // We might have the same clip multiple times in the animation list.
+ // We are removing elements, so we iterate over it backwards.
+ {
+ bool found = false;
+ int i = m_Animations.size();
+ while (i--)
+ {
+ AnimationClip* cur = m_Animations[i];
+ if (cur && cur == &clip)
+ {
+ found = true;
+ Animations::iterator j = m_Animations.begin() + i;
+ m_Animations.erase(j);
+ }
+ }
+ if (!found)
+ {
+ AssertStringObject (Format ("Unable to remove Animation Clip '%s' - clip not found in animation list", clip.GetName()), this);
+ return;
+ }
+ }
+
+ {
+ // Find the animation state(s) to remove
+ // We are removing elements, so we iterate over it backwards.
+ int i = m_AnimationStates.size();
+ while (i--)
+ {
+ if (m_AnimationStates[i]->m_Clip == &clip)
+ {
+ delete m_AnimationStates[i];
+ iterator j = m_AnimationStates.begin() + i;
+ m_AnimationStates.erase(j);
+ }
+ }
+ }
+
+ CheckIsCullingBasedOnBoundsDeprecated ();
+
+ m_DirtyMask |= kRebindDirtyMask;
+}
+
+void Animation::RemoveClip (const std::string &clipName)
+{
+ PROFILER_AUTO(gRemoveClip, this)
+
+ // Find the clip to remove
+ // We might have the same clip multiple times in the animation list.
+ // We are removing elements, so we iterate over it backwards.
+ {
+ bool found = false;
+ int i = m_Animations.size();
+ while (i--)
+ {
+ AnimationClip* cur = m_Animations[i];
+ if (cur && cur->GetName() == clipName)
+ {
+ found = true;
+ Animations::iterator j = m_Animations.begin() + i;
+ m_Animations.erase(j);
+ }
+ }
+ if (!found)
+ {
+ AssertStringObject (Format ("Unable to remove Animation Clip '%s' - clip not found in animation list", clipName.c_str()), this);
+ return;
+ }
+ }
+
+ {
+ // Find the animation state(s) to remove
+ // We are removing elements, so we iterate over it backwards.
+ int i = m_AnimationStates.size();
+ while (i--)
+ {
+ AnimationState *cur = m_AnimationStates[i];
+ if (cur && cur->GetName() == clipName)
+ {
+ delete cur;
+ iterator j = m_AnimationStates.begin() + i;
+ m_AnimationStates.erase(j);
+ }
+ }
+ }
+
+ CheckIsCullingBasedOnBoundsDeprecated();
+
+ m_DirtyMask |= kRebindDirtyMask;
+}
+
+int Animation::GetClipCount () const {
+ return m_Animations.size ();
+}
+
+AnimationState* Animation::GetState(const string& name)
+{
+ BuildAnimationStates();
+
+ for (AnimationStates::iterator i=m_AnimationStates.begin();i!=m_AnimationStates.end();i++)
+ {
+ AnimationState& state = **i;
+ if (state.m_Name == name)
+ return &state;
+ }
+ return NULL;
+}
+
+AnimationState* Animation::GetState(AnimationClip* clip)
+{
+ BuildAnimationStates();
+ for (iterator i=begin();i != end();i++)
+ {
+
+ if ((**i).GetClip() == clip)
+ return *i;
+ }
+ return NULL;
+}
+
+AnimationState* Animation::GetState(AnimationState* state)
+{
+ BuildAnimationStates();
+ for (iterator i=begin();i != end();i++)
+ {
+ if (*i == state)
+ return state;
+ }
+ return NULL;
+}
+
+void Animation::InitializeClass ()
+{
+ AnimationState::InitializeClass();
+ AnimationManager::InitializeClass();
+
+ RegisterAllowNameConversion("Animation", "m_PlayFixedFrameRate", "m_AnimatePhysics");
+ RegisterAllowNameConversion("Animation", "m_AnimateIfVisible", "m_AnimateOnlyIfVisible");
+}
+
+void Animation::CleanupClass ()
+{
+ AnimationState::CleanupClass();
+ AnimationManager::CleanupClass();
+}
+
+
+template<class TransferFunction>
+void Animation::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ transfer.SetVersion (3);
+
+#if UNITY_EDITOR
+ if (transfer.IsOldVersion(1))
+ {
+ TRANSFER_SIMPLE (m_Animation);
+ transfer.Transfer (m_OldAnimations, "m_Animations");
+ TRANSFER_SIMPLE (m_WrapMode);
+ TRANSFER_SIMPLE (m_PlayAutomatically);
+ transfer.Transfer (m_AnimatePhysics, "m_PlayFixedFrameRate");
+ return;
+ }
+#endif
+
+
+ TRANSFER_SIMPLE (m_Animation);
+ TRANSFER_SIMPLE (m_Animations);
+
+ // Hide the wrapmode in the inspector if the user has not changed it already... We are in the process of deprecating it
+#if UNITY_EDITOR
+ bool hide = (transfer.GetFlags () & kSerializeForInspector) != 0 && (transfer.GetFlags () & kSerializeDebugProperties) == 0 && m_WrapMode == 0;
+ transfer.Transfer (m_WrapMode, "m_WrapMode", hide ? kHideInEditorMask : kNoTransferFlags);
+#else
+ transfer.Transfer (m_WrapMode, "m_WrapMode", kNoTransferFlags);
+#endif
+
+ // In Unity 3.4 we switched to the m_CullingType enum. Previously we serialized animateOnlyIfVisible.
+ if (transfer.IsOldVersion(2))
+ {
+ bool animateOnlyIfVisible = false;
+ transfer.Transfer(animateOnlyIfVisible, "m_AnimateOnlyIfVisible");
+ m_CullingType = animateOnlyIfVisible ? kCulling_BasedOnRenderers : kCulling_AlwaysAnimate;
+ }
+
+ TRANSFER_SIMPLE (m_PlayAutomatically);
+ TRANSFER (m_AnimatePhysics);
+
+ transfer.Align();
+
+ // We hide these two fields here, because they are displayed by custom inspector (AnimationEditor)
+ TRANSFER_ENUM(m_CullingType);
+ if (transfer.IsReading())
+ m_CullingType = RemapDeprecatedCullingType(m_CullingType);
+
+
+ TRANSFER_DEBUG(m_AnimationStates);
+}
+
+/*
+
+ TODO:
+
+ * Allow for reimport animations while in playmode!
+
+- Make a list of all mesh users and invalidate them when the mesh changes!
+- implement all missing functions
+- Implement mixing properly (Based on which curves have changed)
+- support delay cross fade (Starts time advancing only when the animation has faded in completely )
+- Don't always add animation component to all game objects! But what if people want to animate a prefab???
+
+- Make the animation system store amount of loops and not one huge float for time?
+- Keep a cache for all curves and pass it into animationcurve.Evaluate.
+ So that many characters dont kill the cache of shared animation curves.
+
+ - automatically Reduce curves that dont affect anything!
+ 1. If all animation clips change that curve to the same value and all curves dont actually modify the value!
+ 2. The current state when loaded is the same as in all curves
+ 3. Only do it for animation clips that got imported from a fbx file
+ - Unity made animations are more likely to be modified by scripting as well
+ so it should not play any tricks on the user
+ 4. Have an option to turn the optimization off. eg someone wants to do IK etc.
+
+---
+- Handle bake simulation better when using clipped animations!
+- auto cleanup of queued animations
+- search for !IsWorldPlaying () those are hacks to get the timeline working
+- timeline doesnt work anymore!
+
+ - Store optimized animation curves in animation clip instead of animation state.
+ - CRASHBUG: Check when animated objects are removed -> rebuild
+ - CRASHBUG: Check when animations change -> rebuild
+
+- Remove normalize fast before blending, not necessary for sampled animations!
+ But what about unity made animations?
+
+
+///@TODO: EXPOSE AnimationState too!(cspreprocess fucks up at the moment)
+- implement manual clipping of animations from scripts
+- Handle stopping and fading out of animations!
+- Add named clip will fail when called at runtime
+
+*/
+