summaryrefslogtreecommitdiff
path: root/Runtime/Animation
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Animation')
-rw-r--r--Runtime/Animation/Animation.cpp2373
-rw-r--r--Runtime/Animation/Animation.h265
-rw-r--r--Runtime/Animation/AnimationBinder.cpp724
-rw-r--r--Runtime/Animation/AnimationBinder.h132
-rw-r--r--Runtime/Animation/AnimationClip.cpp1679
-rw-r--r--Runtime/Animation/AnimationClip.h346
-rw-r--r--Runtime/Animation/AnimationClipBindings.h89
-rw-r--r--Runtime/Animation/AnimationClipSettings.h75
-rw-r--r--Runtime/Animation/AnimationClipStats.h14
-rw-r--r--Runtime/Animation/AnimationClipUtility.cpp73
-rw-r--r--Runtime/Animation/AnimationClipUtility.h6
-rw-r--r--Runtime/Animation/AnimationCurveUtility.cpp1009
-rw-r--r--Runtime/Animation/AnimationCurveUtility.h67
-rw-r--r--Runtime/Animation/AnimationEvent.cpp202
-rw-r--r--Runtime/Animation/AnimationEvent.h29
-rw-r--r--Runtime/Animation/AnimationManager.cpp53
-rw-r--r--Runtime/Animation/AnimationManager.h39
-rw-r--r--Runtime/Animation/AnimationModule.jam169
-rw-r--r--Runtime/Animation/AnimationModuleRegistration.cpp53
-rw-r--r--Runtime/Animation/AnimationSetBinding.cpp563
-rw-r--r--Runtime/Animation/AnimationSetBinding.h74
-rw-r--r--Runtime/Animation/AnimationState.cpp824
-rw-r--r--Runtime/Animation/AnimationState.h269
-rw-r--r--Runtime/Animation/AnimationStateNetworkProvider.cpp51
-rw-r--r--Runtime/Animation/AnimationStateNetworkProvider.h6
-rw-r--r--Runtime/Animation/AnimationUtility.cpp271
-rw-r--r--Runtime/Animation/AnimationUtility.h17
-rw-r--r--Runtime/Animation/Animator.cpp2674
-rw-r--r--Runtime/Animation/Animator.h537
-rw-r--r--Runtime/Animation/AnimatorController.cpp693
-rw-r--r--Runtime/Animation/AnimatorController.h121
-rw-r--r--Runtime/Animation/AnimatorGenericBindings.cpp1100
-rw-r--r--Runtime/Animation/AnimatorGenericBindings.h95
-rw-r--r--Runtime/Animation/AnimatorManager.cpp110
-rw-r--r--Runtime/Animation/AnimatorManager.h28
-rw-r--r--Runtime/Animation/AnimatorOverrideController.cpp302
-rw-r--r--Runtime/Animation/AnimatorOverrideController.h97
-rw-r--r--Runtime/Animation/Avatar.cpp678
-rw-r--r--Runtime/Animation/Avatar.h149
-rw-r--r--Runtime/Animation/AvatarBuilder.cpp807
-rw-r--r--Runtime/Animation/AvatarBuilder.h256
-rw-r--r--Runtime/Animation/AvatarPlayback.cpp118
-rw-r--r--Runtime/Animation/AvatarPlayback.h51
-rw-r--r--Runtime/Animation/BaseAnimationTrack.cpp15
-rw-r--r--Runtime/Animation/BaseAnimationTrack.h21
-rw-r--r--Runtime/Animation/BoundCurve.h51
-rw-r--r--Runtime/Animation/BoundCurveDeprecated.h60
-rw-r--r--Runtime/Animation/CalculateAnimatorSkinMatrices.cpp85
-rw-r--r--Runtime/Animation/CalculateAnimatorSkinMatrices.h18
-rw-r--r--Runtime/Animation/CharacterTestFixture.h202
-rw-r--r--Runtime/Animation/DenseClipBuilder.cpp97
-rw-r--r--Runtime/Animation/DenseClipBuilder.h9
-rw-r--r--Runtime/Animation/EditorCurveBinding.h34
-rw-r--r--Runtime/Animation/GenericAnimationBindingCache.cpp839
-rw-r--r--Runtime/Animation/GenericAnimationBindingCache.h118
-rw-r--r--Runtime/Animation/KeyframeReducer.cpp359
-rw-r--r--Runtime/Animation/KeyframeReducer.h10
-rw-r--r--Runtime/Animation/MecanimAnimation.cpp78
-rw-r--r--Runtime/Animation/MecanimAnimation.h23
-rw-r--r--Runtime/Animation/MecanimArraySerialization.h178
-rw-r--r--Runtime/Animation/MecanimClipBuilder.cpp349
-rw-r--r--Runtime/Animation/MecanimClipBuilder.h128
-rw-r--r--Runtime/Animation/MecanimUtility.cpp45
-rw-r--r--Runtime/Animation/MecanimUtility.h123
-rw-r--r--Runtime/Animation/Motion.cpp28
-rw-r--r--Runtime/Animation/Motion.h42
-rw-r--r--Runtime/Animation/NewAnimationTrack.cpp80
-rw-r--r--Runtime/Animation/NewAnimationTrack.h74
-rw-r--r--Runtime/Animation/OptimizeTransformHierarchy.cpp303
-rw-r--r--Runtime/Animation/OptimizeTransformHierarchy.h50
-rw-r--r--Runtime/Animation/OptimizeTransformHierarchyTests.cpp293
-rw-r--r--Runtime/Animation/PPtrKeyframes.h12
-rw-r--r--Runtime/Animation/RuntimeAnimatorController.cpp66
-rw-r--r--Runtime/Animation/RuntimeAnimatorController.h81
-rw-r--r--Runtime/Animation/ScriptBindings/Animations.txt721
-rw-r--r--Runtime/Animation/ScriptBindings/AnimatorBindings.txt677
-rw-r--r--Runtime/Animation/ScriptBindings/AnimatorOverrideControllerBindings.txt138
-rw-r--r--Runtime/Animation/ScriptBindings/Avatar.txt340
-rw-r--r--Runtime/Animation/ScriptBindings/AvatarBuilderBindings.txt250
-rw-r--r--Runtime/Animation/ScriptBindings/RuntimeAnimatorControllerBindings.txt26
-rw-r--r--Runtime/Animation/StreamedClipBuilder.cpp272
-rw-r--r--Runtime/Animation/StreamedClipBuilder.h16
82 files changed, 23599 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
+
+*/
+
diff --git a/Runtime/Animation/Animation.h b/Runtime/Animation/Animation.h
new file mode 100644
index 0000000..e3091cc
--- /dev/null
+++ b/Runtime/Animation/Animation.h
@@ -0,0 +1,265 @@
+#ifndef ANIMATION_H
+#define ANIMATION_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "BoundCurveDeprecated.h"
+#include "Runtime/Misc/Allocator.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Utilities/vector_set.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Camera/UnityScene.h"
+
+class AnimationState;
+class Transform;
+
+class AnimationClip;
+
+struct QueuedAnimation
+{
+ int mode;
+ int queue;
+ float fadeTime;
+
+ AnimationState* state;
+};
+
+class Animation : public Behaviour
+{
+public:
+ enum CullingType { kCulling_AlwaysAnimate, kCulling_BasedOnRenderers, kDeprecatedCulling_BasedOnClipBounds, kDeprecatedCulling_BasedOnUserBounds };
+
+public:
+ typedef UNITY_VECTOR(kMemAnimation, PPtr<AnimationClip>) Animations;
+ typedef dynamic_array<BoundCurveDeprecated> BoundCurves;
+ typedef UNITY_VECTOR(kMemAnimation, AnimationState*) AnimationStates;
+ typedef AnimationStates::iterator iterator;
+ typedef vector_set<int> SyncedLayers;
+ typedef UNITY_VECTOR(kMemAnimation, Renderer*) ContainedRenderers;
+ typedef UNITY_VECTOR(kMemAnimation, QueuedAnimation) QueuedAnimations;
+ typedef UNITY_VECTOR(kMemAnimation, Transform*) AffectedRootTransforms;
+
+ // Tag class as sealed, this makes QueryComponent faster.
+ static bool IsSealedClass () { return true; }
+
+private:
+ int m_WrapMode;///< enum { Default = 0, Once = 1, Loop = 2, PingPong = 4, ClampForever = 8 }
+ bool m_PlayAutomatically;
+ bool m_AnimatePhysics;
+ bool m_Visible;
+ CullingType m_CullingType; ///< enum { Always Animate = 0, Based On Renderers = 1 }
+
+ /// When we are animating transforms we cache the affect root transforms
+ /// (The top most transform that covers all SendTransformChanged messages that need to sent)
+ AffectedRootTransforms m_CachedAffectedSendToRootTransform;
+ int m_CachedTransformMessageMask;
+ ContainedRenderers m_ContainedRenderers;
+
+ // new stuff
+ BoundCurves m_BoundCurves;
+ AnimationStates m_AnimationStates;
+ AnimationState* m_ActiveAnimationStates[32];
+ int m_ActiveAnimationStatesSize;
+
+ UInt32 m_DirtyMask;
+ ListNode<Animation> m_AnimationManagerNode;
+ SyncedLayers m_SyncedLayers;
+
+ PPtr<AnimationClip> m_Animation;
+ Animations m_Animations;
+
+ QueuedAnimations m_Queued;
+
+#if UNITY_EDITOR
+ typedef std::vector<std::pair<UnityStr, PPtr<AnimationClip> > > OldAnimations;
+ OldAnimations m_OldAnimations;
+#endif
+
+ void PlayClip (AnimationClip& animation, int mode);
+ void RecomputeContainedRenderers ();
+ void RecomputeContainedRenderersRecurse (Transform& transform);
+ void ClearContainedRenderers ();
+ void SampleInternal();
+public:
+
+ REGISTER_DERIVED_CLASS (Animation, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Animation)
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ Animation (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~Animation (); declared-by-macro
+
+ /// Are any animation states playing?
+ /// (Returns true even if the animation state has a zero blend weight)
+ /// (Unaffected by animation.enabled or animation.visible)
+ bool IsPlaying ();
+
+ /// Is the
+ /// (Returns true even if the animation state has a zero blend weight)
+ /// (Unaffected by animation.enabled or animation.visible)
+ bool IsPlaying (const string& name);
+
+ /// Is the animation state playing?
+ /// (Returns true even if the animation state has a zero blend weight)
+ /// (Unaffected by animation.enabled or animation.visible)
+ bool IsPlaying (const AnimationState& state);
+
+ /// Is any animation in the layer playing?
+ /// (Returns true even if the animation state has a zero blend weight)
+ /// (Unaffected by animation.enabled or animation.visible)
+ bool IsPlayingLayer (int layer);
+
+ /// Stops all animations that were started from this component with play or play named!
+ void Stop ();
+ /// Stops all animations that were started from this component with name!
+ void Stop (const string& name);
+ void Stop (AnimationState& state);
+
+
+ void Rewind ();
+ void Rewind (const string& name);
+ void Rewind (AnimationState& state);
+
+ void SyncLayer (int layer) { m_SyncedLayers.insert(layer); }
+
+ void SetWrapMode (int mode);
+ int GetWrapMode () { return m_WrapMode; }
+
+ PPtr<AnimationClip> GetClip () const { return m_Animation; }
+ void SetClip (PPtr<AnimationClip> anim);
+
+ const Animations& GetClips () const { return m_Animations; }
+ void SetClips (const Animations& anims);
+
+// PPtr<AnimationClip> GetNamedClip (const string& name);
+
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+ virtual void Deactivate (DeactivateOperation operation);
+
+ bool GetPlayAutomatically () const { return m_PlayAutomatically; }
+ void SetPlayAutomatically (bool b) { m_PlayAutomatically = b; SetDirty (); }
+
+ void SetCullingType(CullingType type);
+ CullingType GetCullingType() const { return m_CullingType; }
+
+ void CheckRendererVisibleState ();
+
+ // Exposed for Renderer
+ void SetVisibleRenderers(bool visible);
+ // Exposed for UnityScene
+ void SetVisibleBounds(bool visible);
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+ enum PlayMode { kStopSameLayer = 0, kPlayQueuedDeprecated = 1, kPlayMixedDeprecated = 2, kStopAll = 4 };
+// enum PlayMode { kStopAll = 0, kPlayQueued = 1, kPlayMixedDeprecated = 2 };
+
+ bool Play(const std::string& name, int playMode);
+ void Play(AnimationState& fadeIn, int playMode);
+ bool Play(int playMode);
+
+ ///
+ void Blend(const std::string& name, float targetWeight, float time);
+ void Blend(AnimationState& fadeIn, float targetWeight, float time);
+
+ void CrossFade(const std::string& name, float time, int mode);
+ void CrossFade(AnimationState& fadeIn, float time, int mode, bool clearQueuedAnimations );
+
+ enum QueueMode { CompleteOthers = 0, PlayNow = 2 };
+
+ AnimationState* QueueCrossFade(const std::string& name, float time, int queue, int mode);
+ AnimationState* QueueCrossFade(AnimationState& originalState, float time, int queue, int mode);
+
+ AnimationState* QueuePlay(const std::string& name, int queue, int mode) { return QueueCrossFade(name, 0.0F, queue, mode); }
+ AnimationState* QueuePlay(AnimationState& originalState, int queue, int mode) { return QueueCrossFade(originalState, 0.0F, queue, mode); }
+
+ /// Adds an animation clip with name newName to the animation.
+ /// - If newName is not the clip's name or the animation clip needs to be clipped a new instance of the clip will be created.
+ void AddClip (AnimationClip& clip, const std::string& newName, int firstFrame, int lastFrame, bool loop);
+
+ /// Adds an animation clip to the animation. If it already exists this will do nothing.
+ void AddClip (AnimationClip& clip);
+
+ /// Removes an named clip
+ void RemoveClip (AnimationClip& clip);
+ void RemoveClip (const std::string &clipName);
+
+ /// Get the number of clips in the animation
+ int GetClipCount () const;
+
+ void SyncLayerTime (int layer);
+
+ iterator begin () { return m_AnimationStates.begin(); }
+ iterator end () { return m_AnimationStates.end(); }
+
+ /// State management
+ AnimationState& GetAnimationStateAtIndex(int index) { BuildAnimationStates(); return *m_AnimationStates[index]; }
+ int GetAnimationStateCount () { BuildAnimationStates(); return m_AnimationStates.size(); }
+
+ AnimationState* GetState(const std::string& name);
+ AnimationState* GetState(AnimationClip* clip);
+ AnimationState* GetState(AnimationState* state);
+
+ AnimationState* CloneAnimation (AnimationState* state);
+
+ /// Returns the animation clip named name
+ /// - This will onyl search the serialized animation array and not touch animation states at all!
+ AnimationClip* GetClipWithNameSerialized (const std::string& name);
+
+ void UpdateAnimation (double time);
+ void SampleDefaultClip (double time);
+
+ void RebuildStateForEverything();
+ bool RebuildBoundStateMask();
+
+ void Sample();
+
+ void BlendOptimized();
+ void BlendGeneric();
+ void BlendAdditive();
+
+ void SortAnimationStates();
+ void ReleaseAnimationStates();
+
+ void ApplyObjectSlow(Object* lastObject);
+
+#if UNITY_EDITOR
+ void LoadOldAnimations (); // Deprecated with 1.5
+#endif
+ void SendTransformChangedToCachedTransform();
+ void RemoveContainedRenderer (Renderer* renderer);
+
+ void SetAnimatePhysics (bool anim);
+ bool GetAnimatePhysics () { return m_AnimatePhysics; }
+
+ void CleanupBoundCurves ();
+ void ValidateBoundCurves ();
+
+ void EnsureDefaultAnimationIsAdded ();
+
+ AABB GetLocalAABB () const { return AABB::zero; }
+ void SetLocalAABB (const AABB& aabb) { }
+
+ #if UNITY_EDITOR
+ /// Forces the inspector auto refresh without setdirty being called but only in debug mode
+ virtual bool HasDebugmodeAutoRefreshInspector () { return true; }
+ #endif
+
+private:
+
+ AnimationClip* GetClipLegacyWarning (AnimationClip* clip);
+
+ void CheckIsCullingBasedOnBoundsDeprecated() const { Assert(m_CullingType != kDeprecatedCulling_BasedOnClipBounds); Assert(m_CullingType != kDeprecatedCulling_BasedOnUserBounds); }
+ void SetVisibleInternal(bool visible);
+ void BuildAnimationStates();
+
+ void UpdateAnimationInternal_Before32(double time);
+ void UpdateAnimationInternal(double time);
+
+ void UpdateQueuedAnimations_Before34(bool& needsUpdate);
+ void UpdateQueuedAnimations(bool& needsUpdate);
+};
+
+#endif
diff --git a/Runtime/Animation/AnimationBinder.cpp b/Runtime/Animation/AnimationBinder.cpp
new file mode 100644
index 0000000..cd59062
--- /dev/null
+++ b/Runtime/Animation/AnimationBinder.cpp
@@ -0,0 +1,724 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+#include "AnimationBinder.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/Utilities/Word.h"
+#include "Runtime/Serialize/TypeTree.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+#include "Runtime/Animation/AnimatorController.h"
+#include "Runtime/mecanim/animation/avatar.h"
+#if ENABLE_MONO
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+#endif
+#include "External/shaderlab/Library/FastPropertyName.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Utilities/InitializeAndCleanup.h"
+
+using namespace ShaderLab;
+using namespace std;
+
+void AnimationBinder::InitCurveIDLookup (AnimationBinder::CurveIDLookup& curveIDLookup)
+{
+ curveIDLookup.set_empty_key(CurveID("", -1, NULL, "", 0));
+ curveIDLookup.set_deleted_key(CurveID("", -1, NULL, "", 1));
+ curveIDLookup.resize(1024);
+}
+
+inline bool IsAnimatableProperty (const TypeTree* variable, bool isScript, Object* targetObject)
+{
+ if (variable && isScript)
+ {
+ #if ENABLE_MONO
+ MonoBehaviour* behaviour = static_cast<MonoBehaviour*> (targetObject);
+ MonoObject* instance = behaviour->GetInstance();
+ if (instance)
+ {
+ UInt32 offset = reinterpret_cast<UInt8*> (variable->m_DirectPtr) - reinterpret_cast<UInt8*> (instance);
+ UInt32 size = mono_class_instance_size(behaviour->GetClass());
+ return offset < size || variable->m_ByteOffset != -1;
+ }
+ #endif
+ return false;
+ }
+ else if (variable)
+ {
+ return variable->m_ByteOffset != -1;
+ }
+ else
+ return false;
+}
+
+AnimationBinder::~AnimationBinder ()
+{
+ for (TypeTreeCache::iterator i=m_TypeTreeCache.begin();i!=m_TypeTreeCache.end();i++)
+ delete i->second;
+}
+
+inline int GetAnimatableBindType (const TypeTree& variable)
+{
+ if (variable.m_Type == "float")
+ {
+ return kBindFloat;
+ }
+ else if (variable.m_Type == "bool" || (variable.m_Type == "UInt8" && (variable.m_MetaFlag & kEditorDisplaysCheckBoxMask)))
+ {
+ return kBindFloatToBool;
+ }
+ else if (variable.m_Type == "PPtr<Material>")
+ {
+ return kBindMaterialPPtrToRenderer;
+ }
+#if ENABLE_SPRITES
+ else if (variable.m_Type == "PPtr<Sprite>")
+ {
+ return kBindSpritePPtrToSpriteRenderer;
+ }
+#endif
+ else
+ return kUnbound;
+}
+
+
+#if UNITY_EDITOR
+bool AnimationBinder::IsAnimatablePropertyOrHasAnimatableChild (const TypeTree& variable, bool isScript, Object* targetObject)
+{
+ if (variable.m_Children.empty())
+ {
+ if ( IsAnimatableProperty(&variable, isScript, targetObject))
+ {
+ return GetAnimatableBindType(variable) != kUnbound;
+ }
+ }
+
+ for (TypeTree::const_iterator i=variable.begin();i!=variable.end();++i)
+ {
+ if (IsAnimatablePropertyOrHasAnimatableChild(*i, isScript, targetObject))
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+
+static const char* ParseBlendShapeWeightName (const char* attribute)
+{
+ const char* prefix = "blendShape.";
+ if (BeginsWith(attribute, prefix))
+ return attribute + strlen(prefix);
+ else
+ return NULL;
+}
+
+static bool BlendShapeCalculateTargetPtr(Object* targetObject, const std::string& attribute, void** targetPtr, int* type)
+{
+ const char* name = ParseBlendShapeWeightName (attribute.c_str());
+ if (name == NULL)
+ return false;
+
+ SkinnedMeshRenderer* renderer = static_cast<SkinnedMeshRenderer*>(targetObject);
+ Assert(renderer);
+
+ const Mesh* mesh = renderer->GetMesh();
+ if (mesh == NULL)
+ return false;
+
+ const BlendShapeData& blendShapes = mesh->GetBlendShapeData();
+ int index = GetChannelIndex (blendShapes, name);
+ if (index == -1)
+ return false;
+
+ // Encode targetType
+ *type = kBindFloatToBlendShapeWeight | (index << BoundCurveDeprecated::kBindTypeBitCount);
+ *targetPtr = renderer;
+
+ Assert((*type >> BoundCurveDeprecated::kBindTypeBitCount) == index);
+
+ return true;
+}
+
+bool AnimationBinder::CalculateTargetPtr(int classID, Object* targetObject, const char* attribute, void** targetPtr, int* type)
+{
+ Assert(kBindTypeCount <= (1 << BoundCurveDeprecated::kBindTypeBitCount));
+ AssertIf(targetObject == NULL);
+
+ if (classID == ClassID(Transform))
+ {
+ Transform* transformTarget = static_cast<Transform*> (targetObject);
+
+ if (strcmp(attribute, "m_LocalPosition") == 0)
+ {
+ *type = kBindTransformPosition;
+ *targetPtr = &transformTarget->m_LocalPosition;
+ return true;
+ }
+ else if (strcmp(attribute, "m_LocalScale") == 0)
+ {
+ *type = kBindTransformScale;
+ *targetPtr = &transformTarget->m_LocalScale;
+ return true;
+ }
+ else if (strcmp (attribute, "m_LocalRotation") == 0)
+ {
+ *type = kBindTransformRotation;
+ *targetPtr = &transformTarget->m_LocalRotation;
+ return true;
+ }
+ }
+ else if (classID == ClassID(Material))
+ {
+// Renderer* renderer = static_cast<Transform*> (targetObject);
+
+ // [0].mainTex.offset.x
+ // [0].mainTex.offset.y
+ // [0].mainTex.scale.y
+ // [0].mainTex.rotation
+ // [0].mainColor.r
+ // [0].mainColor.x
+ // [0].floatPropertyName
+
+ int materialIndex = 0;
+ int shaderPropertyIndex = 0;
+ int newTargetType = kUnbound;
+ int targetIndex = 0;
+
+ // Grab material index "[3]."
+ const char* a = attribute;
+ if (*a == '[')
+ {
+ while (*a != 0 && *a != '.')
+ a++;
+
+ if (*a == '.')
+ materialIndex = StringToInt(attribute + 1);
+ else
+ return false;
+ attribute = a + 1;
+ }
+
+
+ // Find shader propertyname
+ int dotIndex = -1;
+ const char* lastCharacter;
+ while (*a != 0)
+ {
+ if (*a == '.' && dotIndex == -1)
+ dotIndex = a - attribute;
+ a++;
+ }
+ lastCharacter = a - 1;
+
+ // No . must be float property
+ if (dotIndex == -1)
+ {
+ newTargetType = kBindFloatToMaterial;
+ shaderPropertyIndex = Property(attribute).index;
+ }
+ // Calculate different property types
+ else
+ {
+ shaderPropertyIndex = Property(string(attribute, attribute + dotIndex)).index;
+ attribute += dotIndex + 1;
+
+ switch (*attribute)
+ {
+ // g color or y vector
+ case 'g':
+ case 'y':
+ newTargetType = kBindFloatToColorMaterial;
+ targetIndex = 1;
+ break;
+
+ // b or z vector
+ case 'b':
+ case 'z':
+ newTargetType = kBindFloatToColorMaterial;
+ targetIndex = 2;
+ break;
+
+ // alpha or w vector
+ case 'a':
+ case 'w':
+ newTargetType = kBindFloatToColorMaterial;
+ targetIndex = 3;
+ break;
+
+ // uv scale
+ case 's':
+ newTargetType = kBindFloatToMaterialScaleAndOffset;
+ targetIndex = *lastCharacter == 'x' ? 0 : 1;
+ break;
+ // uv offset
+ case 'o':
+ newTargetType = kBindFloatToMaterialScaleAndOffset;
+ targetIndex = *lastCharacter == 'x' ? 2 : 3;
+ break;
+
+ // r color
+ case 'r':
+ if (lastCharacter == attribute)
+ {
+ newTargetType = kBindFloatToColorMaterial;
+ targetIndex = 0;
+ }
+ break;
+ // x vector
+ case 'x':
+ newTargetType = kBindFloatToColorMaterial;
+ targetIndex = 0;
+ break;
+ }
+ }
+
+ if (newTargetType != kUnbound)
+ {
+ Assert(BoundCurveDeprecated::kBindTypeBitCount + BoundCurveDeprecated::kBindMaterialShaderPropertyNameBitCount < 32);
+ Assert(newTargetType < (1 << BoundCurveDeprecated::kBindTypeBitCount));
+ Assert(shaderPropertyIndex < (1 << BoundCurveDeprecated::kBindMaterialShaderPropertyNameBitCount));
+ Assert(targetIndex < (1 << (32 - BoundCurveDeprecated::kBindTypeBitCount - BoundCurveDeprecated::kBindMaterialShaderPropertyNameBitCount)));
+
+ // Encode targetType
+ newTargetType |= targetIndex << (BoundCurveDeprecated::kBindMaterialShaderPropertyNameBitCount + BoundCurveDeprecated::kBindTypeBitCount);
+ newTargetType |= shaderPropertyIndex << BoundCurveDeprecated::kBindTypeBitCount;
+ *targetPtr = reinterpret_cast<void*> (materialIndex);
+ *type = newTargetType;
+ return true;
+ }
+ else
+ {
+ *targetPtr = NULL;
+ *type = kUnbound;
+ return false;
+ }
+ }
+ else if (classID == ClassID(GameObject))
+ {
+ if (strcmp(attribute, "m_IsActive") == 0)
+ {
+ *type = kBindFloatToGameObjectActivate;
+ *targetPtr = targetObject;
+ return true;
+ }
+ }
+ else if (classID == ClassID(SkinnedMeshRenderer))
+ {
+ // We do not return on false, because this paths handles only animation for "blendShapeWeights[i]"
+ // and user might want to animate something else
+ if (BlendShapeCalculateTargetPtr(targetObject, attribute, targetPtr, type))
+ return true;
+ }
+
+ bool isScript =
+ #if ENABLE_MONO
+ classID == ClassID(MonoBehaviour);
+ #else
+ false;
+ #endif
+
+ TypeTree* typeTree = NULL;
+ if (m_TypeTreeCache.count(classID))
+ typeTree = m_TypeTreeCache.find(classID)->second;
+ else
+ {
+ // Build proxy
+ typeTree = new TypeTree();
+ GenerateTypeTree (*targetObject, typeTree);
+ if (!isScript)
+ m_TypeTreeCache[classID] = typeTree;
+ }
+
+ *type = kUnbound;
+ *targetPtr = NULL;
+
+ // Find attribute
+ // * Check if we support binding that value
+ // * scripts use direct ptrs but it only works reliable at the root level, because other variables may be moved/deleted arbitrarily
+ const TypeTree* variable = FindAttributeInTypeTreeNoArrays (*typeTree, attribute);
+
+ if (IsAnimatableProperty(variable, isScript, targetObject))
+ {
+ *type = GetAnimatableBindType(*variable);
+
+ if (*type != kUnbound)
+ {
+ if (variable->m_ByteOffset != -1)
+ *targetPtr = reinterpret_cast<UInt8*>(targetObject) + variable->m_ByteOffset;
+ else
+ *targetPtr = variable->m_DirectPtr;
+ }
+ }
+
+ if (isScript)
+ delete typeTree;
+
+ return *type != kUnbound;
+}
+
+
+//@TODO: Stop supporting this. Only support batched BindCurve
+bool AnimationBinder::BindCurve (const CurveID& curveID, BoundCurveDeprecated& bound, Transform& transform)
+{
+ // Lookup without path
+ Object* targetObject = NULL;
+ Transform* child = &transform;
+ if (curveID.path[0] != '\0')
+ {
+ child = FindRelativeTransformWithPath(*child, curveID.path);
+ if (child == NULL)
+ return false;
+ }
+
+ // Lookup gameobject
+ if (curveID.classID == ClassID(GameObject))
+ {
+ targetObject = &child->GetGameObject();
+ }
+ // Lookup material
+ else if (curveID.classID == ClassID(Material))
+ {
+ targetObject = GetComponentWithScript(child->GetGameObject(), ClassID(Renderer), curveID.script);
+ if (targetObject == NULL)
+ return false;
+ }
+ // Lookup component
+ else if (curveID.classID != ClassID(Material))
+ {
+ targetObject = GetComponentWithScript(child->GetGameObject(), curveID.classID, curveID.script);
+ if (targetObject == NULL)
+ return false;
+ }
+
+ int type;
+ void* targetPtr;
+ if (!CalculateTargetPtr(curveID.classID, targetObject, curveID.attribute, &targetPtr, &type))
+ return false;
+
+ bound.targetPtr = reinterpret_cast<UInt8*>(targetPtr);
+ bound.targetType = type;
+ bound.targetObject = targetObject;
+ bound.targetInstanceID = targetObject->GetInstanceID();
+
+ return true;
+}
+
+static void ClearTransformTemporaryFlag (Transform& transform)
+{
+ transform.SetTemporaryFlags(0);
+ Transform::iterator end = transform.end();
+ for (Transform::iterator i=transform.begin();i!=end;i++)
+ ClearTransformTemporaryFlag(**i);
+}
+
+static void CalculateTransformRoots (Transform& transform, AnimationBinder::AffectedRootTransforms& affectedRootTransforms)
+{
+ if (transform.GetTemporaryFlags())
+ {
+ affectedRootTransforms.push_back(&transform);
+ }
+ else
+ {
+ Transform::iterator end = transform.end();
+ for (Transform::iterator i=transform.begin();i!=end;i++)
+ CalculateTransformRoots(**i, affectedRootTransforms);
+ }
+}
+
+void AnimationBinder::RemoveUnboundCurves (CurveIDLookup& lookup, BoundCurves& outBoundCurves)
+{
+ CurveIDLookup::iterator i;
+ // Some curves couldn't be bound. We want to avoid the runtime check so we remove from the lookup and boundcurves array completely.
+ // - we already removed them from the lookup table
+ // - Now we need to remap the bound curves and
+ if (lookup.size() != outBoundCurves.size())
+ {
+ if (lookup.empty())
+ {
+ outBoundCurves.clear();
+ return;
+ }
+
+ BoundCurves tempBoundCurves;
+ tempBoundCurves.resize_uninitialized(lookup.size());
+
+ // Build a remap table that will compact the array - erasing the curves that are undefined
+ vector<int> remap;
+ remap.resize(outBoundCurves.size());
+ int validCount = 0;
+ for (int j=0;j<outBoundCurves.size();j++)
+ {
+ remap[j] = validCount;
+ if (outBoundCurves[j].targetType != kUnbound)
+ {
+ tempBoundCurves[validCount] = outBoundCurves[j];
+ validCount++;
+ }
+ }
+
+ for (i=lookup.begin();i != lookup.end();i++)
+ i->second = remap[i->second];
+
+ tempBoundCurves.swap(outBoundCurves);
+ }
+}
+
+void AnimationBinder::BindCurves (const CurveIDLookup& lookup, GameObject& rootGameObject, BoundCurves& outBoundCurves)
+{
+ AffectedRootTransforms affectedRoot;
+ int transformMessageMask = 0;
+ BindCurves(lookup, rootGameObject.GetComponent(Transform), outBoundCurves, affectedRoot, transformMessageMask);
+}
+
+
+
+void AnimationBinder::BindCurves (const CurveIDLookup& lookup, Transform& transform, BoundCurves& outBoundCurves, AffectedRootTransforms& affectedRootTransforms, int& transformChangedMask)
+{
+ outBoundCurves.resize_uninitialized(lookup.size());
+ affectedRootTransforms.clear();
+ transformChangedMask = 0;
+ ClearTransformTemporaryFlag(transform);
+
+ // Go through all lookups. Find their binder and assign it.
+ CurveIDLookup::const_iterator next, i;
+ for (i=lookup.begin();i != lookup.end();i=next)
+ {
+ next = i;
+ next++;
+
+ const CurveID& curveID = i->first;
+ int bindIndex = i->second;
+
+ outBoundCurves[bindIndex].targetPtr = NULL;
+ outBoundCurves[bindIndex].targetObject = NULL;
+ outBoundCurves[bindIndex].targetInstanceID = 0;
+ outBoundCurves[bindIndex].targetType = kUnbound;
+
+ // Lookup without path
+ Object* targetObject = NULL;
+ GameObject* go = NULL;
+ if (curveID.path[0] != '\0')
+ {
+ // Lookup child
+ Transform* child = FindRelativeTransformWithPath(transform, curveID.path);
+ if (child == NULL)
+ {
+ #if DEBUG_ANIMATIONS
+ LogString(Format("Animation bind couldn't find transform child %s", curveID.path));
+ #endif
+ #if COMPACT_UNBOUND_CURVES
+ lookup.erase(i);
+ #endif
+ continue;
+ }
+
+ go = &child->GetGameObject();
+
+ }
+ else
+ {
+ go = &transform.GetGameObject();
+ }
+
+ // Lookup component
+
+ if (curveID.classID == ClassID(GameObject))
+ {
+ targetObject = go;
+ }
+ else if (curveID.classID != ClassID(Material))
+ {
+ targetObject = GetComponentWithScript(*go, curveID.classID, curveID.script);
+ if (targetObject == NULL)
+ {
+ #if DEBUG_ANIMATIONS
+ LogString(Format("Animation couldn't find %s", Object::ClassIDToString(curveID.classID)));
+ #endif
+ #if COMPACT_UNBOUND_CURVES
+ lookup.erase(i);
+ #endif
+ continue;
+ }
+ }
+ // Lookup material
+ else
+ {
+ targetObject = GetComponentWithScript(*go, ClassID(Renderer), curveID.script);
+ if (targetObject == NULL)
+ {
+ #if DEBUG_ANIMATIONS
+ LogString(Format("Animation couldn't find %s", Object::ClassIDToString(curveID.classID)));
+ #endif
+ #if COMPACT_UNBOUND_CURVES
+ lookup.erase(i);
+ #endif
+ continue;
+ }
+ }
+
+
+ int type;
+ void* targetPtr;
+ if (!CalculateTargetPtr(curveID.classID, targetObject, curveID.attribute, &targetPtr, &type))
+ {
+ #if DEBUG_ANIMATIONS
+ LogString(Format("Couldn't bind animation attribute %s.%s", Object::ClassIDToString(curveID.classID).c_str(), curveID.attribute));
+ #endif
+ #if COMPACT_UNBOUND_CURVES
+ lookup.erase(i);
+ #endif
+ continue;
+ }
+
+ // - Precalculate affected root transform (Where do we send the transform changed message to)
+ // - Precalculated transform changed mask (What part of the transform has changed)
+ if (curveID.classID == ClassID(Transform))
+ {
+ targetObject->SetTemporaryFlags(1);
+
+ if ((transformChangedMask & Transform::kRotationChanged) == 0 && BeginsWith(curveID.attribute, "m_LocalRotation"))
+ transformChangedMask |= Transform::kRotationChanged;
+ if ((transformChangedMask & Transform::kPositionChanged) == 0 && BeginsWith(curveID.attribute, "m_LocalPosition"))
+ transformChangedMask |= Transform::kPositionChanged;
+ if ((transformChangedMask & Transform::kScaleChanged) == 0 && BeginsWith(curveID.attribute, "m_LocalScale"))
+ transformChangedMask |= Transform::kScaleChanged;
+ }
+
+ outBoundCurves[bindIndex].targetPtr = targetPtr;
+ outBoundCurves[bindIndex].targetType = type;
+ outBoundCurves[bindIndex].targetObject = targetObject;
+ outBoundCurves[bindIndex].targetInstanceID = targetObject->GetInstanceID();
+
+ #if DEBUG_ANIMATIONS
+ outBoundCurves[bindIndex].attribute = curveID.attribute;
+ outBoundCurves[bindIndex].path = curveID.path;
+ outBoundCurves[bindIndex].klass = Object::ClassIDToString(curveID.classID);
+ #endif
+ }
+
+ CalculateTransformRoots (transform, affectedRootTransforms);
+}
+
+AnimationBinder* AnimationBinder::s_Instance = NULL;
+
+void AnimationBinder::StaticInitialize()
+{
+ s_Instance = UNITY_NEW(AnimationBinder, kMemAnimation);
+}
+
+void AnimationBinder::StaticDestroy()
+{
+ UNITY_DELETE(s_Instance, kMemAnimation);
+}
+
+static RegisterRuntimeInitializeAndCleanup s_AnimationBinderCallbacks(AnimationBinder::StaticInitialize, AnimationBinder::StaticDestroy);
+
+AnimationBinder& GetAnimationBinder()
+{
+ return *AnimationBinder::s_Instance;
+}
+
+
+inline Material* GetInstantiatedMaterial (const BoundCurveDeprecated& bind)
+{
+ unsigned int materialIndex = reinterpret_cast<intptr_t> (bind.targetPtr);
+ Renderer* renderer = static_cast<Renderer*> (bind.targetObject);
+
+ if (materialIndex < renderer->GetMaterialCount())
+ return renderer->GetAndAssignInstantiatedMaterial(materialIndex, true);
+ else
+ return NULL;
+}
+
+bool AnimationBinder::SetFloatValue (const BoundCurveDeprecated& bind, float value)
+{
+ UInt32 targetType = bind.targetType;
+ Assert(bind.targetType != kUnbound && bind.targetType != kBindTransformRotation && bind.targetType != kBindTransformPosition && bind.targetType != kBindTransformScale);
+
+ targetType &= BoundCurveDeprecated::kBindTypeMask;
+
+ if( targetType == kBindFloat )
+ {
+ *reinterpret_cast<float*>(bind.targetPtr) = value;
+ return true;
+ }
+ else if( targetType == kBindFloatToGameObjectActivate )
+ {
+ bool activeState = AnimationFloatToBool(value);
+ GameObject* go = static_cast<GameObject*> (bind.targetObject);
+ go->SetSelfActive (activeState);
+
+ return true;
+ }
+ else if( targetType == kBindFloatToBool )
+ {
+ *reinterpret_cast<UInt8*>(bind.targetPtr) = AnimationFloatToBool(value);
+ return true;
+ }
+ else if (targetType == kBindFloatToBlendShapeWeight)
+ {
+ SkinnedMeshRenderer* renderer = reinterpret_cast<SkinnedMeshRenderer*>(bind.targetObject);
+
+ const int shapeIndex = bind.targetType >> BoundCurveDeprecated::kBindTypeBitCount;
+ renderer->SetBlendShapeWeight(shapeIndex, value);
+ return true;
+ }
+ else
+ {
+ Material* material = GetInstantiatedMaterial (bind);
+ if (material != NULL)
+ {
+ targetType = bind.targetType;
+
+ // Extract value index, shader property name and real targetType
+ int valueIndex = (targetType >> 28) & 0xF;
+ int propertyName = (targetType >> 4) & 0xFFFFF;
+ targetType = targetType & 0xF;
+
+ ShaderLab::FastPropertyName name;
+ name.index = propertyName;
+ if (targetType == kBindFloatToMaterial)
+ material->SetFloat(name, value);
+ else if (targetType == kBindFloatToMaterialScaleAndOffset)
+ material->SetTextureScaleAndOffsetIndexed(name, valueIndex, value);
+ else if (targetType == kBindFloatToColorMaterial)
+ material->SetColorIndexed(name, valueIndex, value);
+ else
+ {
+ AssertString("Unsupported bind mode!");
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
+
+void AnimationBinder::SetValueAwakeGeneric (const BoundCurveDeprecated& bind)
+{
+ if (ShouldAwakeGeneric(bind))
+ {
+ bind.targetObject->AwakeFromLoad(kDefaultAwakeFromLoad);
+ bind.targetObject->SetDirty();
+ }
+}
+
+int AnimationBinder::InsertCurveIDIntoLookup (CurveIDLookup& curveIDLookup, const CurveID& curveID)
+{
+ return curveIDLookup.insert(std::make_pair(curveID, curveIDLookup.size())).first->second;
+}
+
+void CurveID::CalculateHash ()
+{
+ hash_cstring h;
+ hash = max(h (path) ^ ClassID(Transform) ^ h (attribute), (unsigned)2);
+}
+
+
+
diff --git a/Runtime/Animation/AnimationBinder.h b/Runtime/Animation/AnimationBinder.h
new file mode 100644
index 0000000..c5a4f13
--- /dev/null
+++ b/Runtime/Animation/AnimationBinder.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "BoundCurveDeprecated.h"
+
+class Transform;
+class TypeTree;
+class MonoScript;
+namespace Unity { class GameObject; class Material; }
+
+#include <string>
+#include <map>
+#include <vector>
+#include "Runtime/Utilities/dense_hash_map.h"
+#include "Runtime/Utilities/CStringHash.h"
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Misc/Allocator.h"
+#include "Runtime/Mono/MonoScript.h"
+
+struct CurveID
+{
+ const char* path;
+ int classID;
+ const char* attribute;
+ MonoScriptPtr script;
+ unsigned hash;
+
+ CurveID () {}
+ CurveID (const char* inPath, int inClassID, MonoScriptPtr inScript, const char* inAttribute, unsigned inHash)
+ {
+ path = inPath;
+ attribute = inAttribute;
+ classID = inClassID;
+ script = inScript;
+ hash = inHash;
+ }
+
+ friend bool operator == (const CurveID& lhs, const CurveID& rhs)
+ {
+ if (lhs.hash == rhs.hash && lhs.classID == rhs.classID)
+ {
+ int pathCompare = strcmp(lhs.path, rhs.path);
+ if (pathCompare == 0)
+ {
+ int attributeCompare = strcmp(lhs.attribute, rhs.attribute);
+ if (attributeCompare == 0)
+ return lhs.script == rhs.script;
+ else
+ return false;
+ }
+ else
+ return false;
+ }
+ return false;
+ }
+
+ void CalculateHash ();
+};
+
+struct hash_curve
+{
+ unsigned operator()(const CurveID& curve) const
+ {
+ return curve.hash;
+ }
+};
+
+
+class AnimationBinder
+{
+ typedef std::map<int, TypeTree*> TypeTreeCache;
+ TypeTreeCache m_TypeTreeCache;
+
+
+ friend AnimationBinder& GetAnimationBinder();
+ static AnimationBinder* s_Instance;
+
+public:
+ typedef std::pair<const CurveID, unsigned> CurveIntPair;
+ typedef STL_ALLOCATOR(kMemTempAlloc, CurveIntPair) TempCurveIDAllocator;
+ typedef dense_hash_map<CurveID, unsigned, hash_curve, std::equal_to<CurveID>, TempCurveIDAllocator > CurveIDLookup;
+ typedef dynamic_array<BoundCurveDeprecated> BoundCurves;
+ typedef UNITY_VECTOR(kMemAnimation, Transform*) AffectedRootTransforms;
+
+ AnimationBinder () { }
+ ~AnimationBinder ();
+
+ static void StaticInitialize ();
+ static void StaticDestroy ();
+
+ bool CalculateTargetPtr(int classID, Object* targetObject, const char* attribute, void** targetPtr, int* type);
+
+ // Builds outBoundCurves and generates all binding information
+ // NOTE: If a curves can not be bound to the target objects, the entry will be removed from the lookup and will also not be in outBoundCurves
+ void BindCurves (const CurveIDLookup& lookup, Transform& transform, BoundCurves& outBoundCurves, AffectedRootTransforms& affectedRootTransforms, int& transformChangedMask);
+ void BindCurves (const CurveIDLookup& lookup, Unity::GameObject& rootGameObject, BoundCurves& outBoundCurves);
+
+ static void RemoveUnboundCurves (CurveIDLookup& lookup, BoundCurves& outBoundCurves);
+
+ static void InitCurveIDLookup (CurveIDLookup& lookup);
+ static int InsertCurveIDIntoLookup (CurveIDLookup& lookup, const CurveID& curveIDLookup);
+
+ // Simplified curve binding. No support for materials
+ bool BindCurve (const CurveID& curveID, BoundCurveDeprecated& bound, Transform& transform);
+
+
+ // Sets the value on the bound curve.
+ // Does not call AwakeFromLoad or SetDirty. You can call SetValueAwakeGeneric or do it yourself.
+ static bool SetFloatValue (const BoundCurveDeprecated& bind, float value);
+
+ // Calls AwakeFromLoad or SetDirty on the target
+ static void SetValueAwakeGeneric (const BoundCurveDeprecated& bind);
+
+ static bool ShouldAwakeGeneric (const BoundCurveDeprecated& bind) { return bind.targetType == kBindFloat || bind.targetType == kBindFloatToBool; }
+
+ static inline bool AnimationFloatToBool (float result)
+ {
+ return result > 0.001F || result < -0.001F;
+ }
+
+ static inline float AnimationBoolToFloat (bool value)
+ {
+ return value ? 1.0F : 0.0F;
+ }
+
+ #if UNITY_EDITOR
+
+ static bool IsAnimatablePropertyOrHasAnimatableChild (const TypeTree& variable, bool isScript, Object* targetObject);
+
+ #endif
+};
+
+AnimationBinder& GetAnimationBinder();
diff --git a/Runtime/Animation/AnimationClip.cpp b/Runtime/Animation/AnimationClip.cpp
new file mode 100644
index 0000000..e97098c
--- /dev/null
+++ b/Runtime/Animation/AnimationClip.cpp
@@ -0,0 +1,1679 @@
+#include "UnityPrefix.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "AnimationClip.h"
+#include "AnimationBinder.h"
+#include "NewAnimationTrack.h"
+#include "AnimationCurveUtility.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Utilities/algorithm_utility.h"
+#include <limits>
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Filters/Mesh/CompressedMesh.h"
+#include "Runtime/mecanim/generic/stringtable.h"
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+#include "Runtime/Serialize/SerializeUtility.h"
+#include "GenericAnimationBindingCache.h"
+#include "AnimationClipStats.h"
+#include "MecanimClipBuilder.h"
+#include "MecanimUtility.h"
+
+
+
+#if UNITY_EDITOR
+#include "KeyframeReducer.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#endif
+
+using namespace UnityEngine::Animation;
+
+static AnimationClip::DidModifyClipCallback* gDidModifyClipCallback = NULL;
+
+#if UNITY_EDITOR
+static AnimationClip::OnAnimationClipAwake* gOnAnimationClipAwake = NULL;
+void AnimationClip::SetOnAnimationClipAwake (OnAnimationClipAwake* callback)
+{
+ gOnAnimationClipAwake = callback;
+}
+
+#endif
+
+using namespace std;
+
+AnimationClip::AnimationClip(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode),
+m_Bounds(Vector3f::zero, Vector3f::zero),
+m_AnimationType(kLegacy),
+m_MuscleClipSize(0),
+m_MuscleClip(0),
+m_UseHighQualityCurve(true),
+m_ClipAllocator(4*1024)
+{
+ m_SampleRate = 60.0F;
+ m_Compressed = false;
+ m_WrapMode = 0;
+}
+
+void AnimationClip::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+ #if UNITY_EDITOR
+
+ ConvertToNewCurveFormat();
+ if (gOnAnimationClipAwake)
+ gOnAnimationClipAwake (this);
+
+
+ // Generate muscle clip data from editor curves (Assetbundles already have a muscle clip baked in, we dont want to regenerate that)
+ if ((mode & kDidLoadFromDisk) && GetRuntimeAsset() != NULL)
+ {
+ // case 476382, need to populate m_AnimationClipSettings manually because it is not transfer for game release asset
+ CstToAnimationClipSettings(m_MuscleClip, m_AnimationClipSettings);
+ }
+ // When loading scenes etc it's a good idea not to have the hiccups all on first access
+ else if ((mode & kDidLoadFromDisk) && GetRuntimeAsset() == NULL)
+ GenerateMuscleClip();
+ else
+ CleanupMecanimData();
+
+ #endif
+
+ ClipWasModified (false);
+}
+
+void AnimationClip::ClipWasModifiedAndUpdateMuscleRange ()
+{
+ ClipWasModified();
+ UpdateMuscleClipRange ();
+}
+
+void AnimationClip::UpdateMuscleClipRange ()
+{
+#if UNITY_EDITOR
+ m_CachedRange = make_pair (std::numeric_limits<float>::infinity (), -std::numeric_limits<float>::infinity ());
+ pair<float,float> range = GetRange();
+
+ AnimationClipSettings settings = GetAnimationClipSettings ();
+ settings.m_StartTime = 0.0F;
+ settings.m_StopTime = range.second;
+
+ SetAnimationClipSettingsNoDirty(settings);
+#endif
+}
+
+void AnimationClip::SetSampleRate (float s)
+{
+ m_SampleRate = s;
+ // Stop time depends on the sample rate
+ // because we add an extra frame at the end to cover pptr curves
+ UpdateMuscleClipRange ();
+ SetDirty();
+}
+
+void AnimationClip::CheckConsistency ()
+{
+ Super::CheckConsistency();
+
+ if(kLegacy > m_AnimationType || m_AnimationType > kHumanoid)
+ {
+ CleanupMecanimData();
+ m_AnimationType = kLegacy;
+ }
+}
+
+void AnimationClip::ClipWasModified (bool cleanupMecanimData)
+{
+ if (cleanupMecanimData)
+ CleanupMecanimData();
+
+ NotifyObjectUsers(kDidModifyMotion);
+
+ m_CachedRange = make_pair (std::numeric_limits<float>::infinity (), -std::numeric_limits<float>::infinity ());
+ gDidModifyClipCallback (this, m_AnimationStates);
+}
+
+void AnimationClip::ClearCurves ()
+{
+ m_RotationCurves.clear();
+ m_PositionCurves.clear();
+ m_ScaleCurves.clear();
+ m_FloatCurves.clear();
+ m_PPtrCurves.clear();
+ #if UNITY_EDITOR
+ m_EditorCurves.clear();
+ m_EulerEditorCurves.clear();
+ #endif
+
+ ClipWasModifiedAndUpdateMuscleRange ();
+ SetDirty();
+}
+
+void AnimationClip::EnsureQuaternionContinuity ()
+{
+ for (QuaternionCurves::iterator i=m_RotationCurves.begin();i != m_RotationCurves.end();i++)
+ ::EnsureQuaternionContinuityAndRecalculateSlope(i->curve);
+
+ gDidModifyClipCallback (this, m_AnimationStates);
+ SetDirty();
+}
+
+bool AnimationClip::HasAnimationEvents ()
+{
+ return m_Events.size() > 0;
+}
+
+void AnimationClip::FireAnimationEvents (float lastTime, float now, Unity::Component& source)
+{
+ AnimationClip::Events& events = GetEvents();
+ Assert(!events.empty());
+
+ if (lastTime == now)
+ return;
+
+ ///@TODO:
+ // * AnimationEvents with blendWeight = 0 will not be fired (In a blendtree they should always be fired, irregardless of blendweight)
+
+
+ int eventCount = events.size();
+
+ // Simple forward playback.
+ if (lastTime < now)
+ {
+ // Special case for first frame in the clip, when just playing a simple clip
+ // We want to make sure the event on the first frame will be fired.
+ // (We can't have this in the general codepath otherwise we end up firing events twice,
+ // if the event matches the time exactly)
+ if (lastTime == 0.0F && events[0].time == 0.0F)
+ FireEvent(events[0], 0, source);
+
+ // Play all events that
+ for (int eventIter = 0; eventIter < eventCount; eventIter++)
+ {
+ if (lastTime < events[eventIter].time && now >= events[eventIter].time)
+ FireEvent(events[eventIter], 0, source);
+ }
+ }
+ // Looping
+ // - Play all events to the end of the clip
+ // - then from first event up to now
+ else
+ {
+ for (int eventIter = 0; eventIter < eventCount; eventIter++)
+ {
+ if (lastTime < events[eventIter].time)
+ FireEvent(events[eventIter], 0, source);
+ }
+
+ for (int eventIter = 0; eventIter < eventCount; eventIter++)
+ {
+ if (now > events[eventIter].time)
+ FireEvent(events[eventIter], 0, source);
+ }
+ }
+}
+
+#if UNITY_EDITOR
+void AnimationClip::ReloadEditorEulerCurves (const string& path)
+{
+ SET_ALLOC_OWNER(this);
+ // Find the euler curves we will use to build the quaternion curves
+ AnimationCurve* curves[4] = { NULL, NULL, NULL, NULL };
+ for (FloatCurves::iterator i=m_EditorCurves.begin();i != m_EditorCurves.end();i++)
+ {
+ if (BeginsWith(i->attribute, "m_LocalRotation") && i->path == path)
+ {
+ char last = i->attribute[i->attribute.size()-1];
+ if (last == 'x')
+ curves[0] = &i->curve;
+ else if (last == 'y')
+ curves[1] = &i->curve;
+ else if (last == 'z')
+ curves[2] = &i->curve;
+ else if (last == 'w')
+ curves[3] = &i->curve;
+ else
+ {
+ ErrorString("Can't set curve because " + i->attribute + " is not a valid Transform property.");
+ continue;
+ }
+ }
+ }
+
+ // Remove existing euler curves at path
+ for (int i=0;i != m_EulerEditorCurves.size();i++)
+ {
+ if (m_EulerEditorCurves[i].classID == ClassID(Transform) && m_EulerEditorCurves[i].path == path)
+ {
+ m_EulerEditorCurves[i] = m_EulerEditorCurves.back();
+ m_EulerEditorCurves.resize(m_EulerEditorCurves.size() - 1);
+ i--;
+ }
+ }
+
+ if (curves[0] && curves[1] && curves[2] && curves[3])
+ {
+ // Create combined quaternion curve
+ AnimationCurveQuat quatCurve;
+ CombineCurve (*curves[0], 0, quatCurve);
+ CombineCurve (*curves[1], 1, quatCurve);
+ CombineCurve (*curves[2], 2, quatCurve);
+ CombineCurve (*curves[3], 3, quatCurve);
+
+ // Create euler curves
+ FloatCurve curve;
+ curve.path = path;
+ curve.classID = ClassID(Transform);
+ int first = m_EulerEditorCurves.size();
+ curve.attribute = "localEulerAngles.x";
+ m_EulerEditorCurves.push_back(curve);
+ curve.attribute = "localEulerAngles.y";
+ m_EulerEditorCurves.push_back(curve);
+ curve.attribute = "localEulerAngles.z";
+ m_EulerEditorCurves.push_back(curve);
+ AnimationCurve* eulerCurves[3] = { &m_EulerEditorCurves[first].curve, &m_EulerEditorCurves[first+1].curve, &m_EulerEditorCurves[first+2].curve };
+
+ QuaternionCurveToEulerCurve(quatCurve, eulerCurves);
+ }
+}
+
+void AnimationClip::ReloadEditorQuaternionCurves (const string& path)
+{
+ SET_ALLOC_OWNER(this);
+ int bakedEulerCurves = -1;
+
+ // Find the euler curves we will use to build the quaternion curves
+ AnimationCurve* curves[3] = { NULL, NULL, NULL };
+ for (FloatCurves::iterator i=m_EulerEditorCurves.begin();i != m_EulerEditorCurves.end();i++)
+ {
+ char last = i->attribute[i->attribute.size()-1];
+
+ if (i->path == path)
+ {
+ DebugAssertIf(!BeginsWith(i->attribute, "localEulerAngles") && !BeginsWith(i->attribute, "localEulerAnglesBaked"));
+ int curBaked = BeginsWith(i->attribute, "localEulerAnglesBaked");
+ if (bakedEulerCurves != -1 && bakedEulerCurves != curBaked)
+ {
+ ErrorString("localEulerAnglesBaked and localEulerAngles exist at the same time. Please ensure that there is always only one curve type in use on a single transform.");
+ continue;
+ }
+
+ bakedEulerCurves = curBaked;
+
+ if (last == 'x')
+ curves[0] = &i->curve;
+ else if (last == 'y')
+ curves[1] = &i->curve;
+ else if (last == 'z')
+ curves[2] = &i->curve;
+ else
+ {
+ ErrorString("Can't set curve because " + i->attribute + " is not a valid Transform property.");
+ continue;
+ }
+ }
+ }
+
+ // Remove existing quaternion curves at path
+ for (int i=0;i != m_EditorCurves.size();i++)
+ {
+ if (m_EditorCurves[i].classID == ClassID(Transform) && BeginsWith(m_EditorCurves[i].attribute, "m_LocalRotation") && m_EditorCurves[i].path == path)
+ {
+ m_EditorCurves[i] = m_EditorCurves.back();
+ m_EditorCurves.resize(m_EditorCurves.size() - 1);
+ i--;
+ }
+ }
+
+ // If all 3 euler curves exist, add the quaternion editor curve
+ if (curves[0] && curves[1] && curves[2])
+ {
+ // Create temporary quaternion curve
+ AnimationCurveQuat quatCurve;
+
+ // TODO : these both should use tangents or fitting
+ if (bakedEulerCurves)
+ EulerToQuaternionCurveBake(*curves[0], *curves[1], *curves[2], quatCurve, GetSampleRate());
+ else
+ EulerToQuaternionCurve(*curves[0], *curves[1], *curves[2], quatCurve);
+
+ // Create quaternion editor curves from single quaternion curve
+ FloatCurve curve;
+ curve.path = path;
+ curve.classID = ClassID(Transform);
+
+ int first = m_EditorCurves.size();
+ curve.attribute = "m_LocalRotation.x";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.y";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.z";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.w";
+ m_EditorCurves.push_back(curve);
+
+ AnimationCurve* quaternionCurves[4] = { &m_EditorCurves[first].curve, &m_EditorCurves[first+1].curve, &m_EditorCurves[first+2].curve, &m_EditorCurves[first+3].curve };
+ ExpandQuaternionCurve(quatCurve, quaternionCurves);
+ }
+}
+
+void AnimationClip::SetEditorCurve (const string& path, int classID, MonoScriptPPtr script, const std::string& attribute, const AnimationCurve* curve, bool syncEditorCurves)
+{
+ SET_ALLOC_OWNER(this);
+ GetEditorCurvesSync();
+
+ FloatCurves* curveArray = &m_EditorCurves;
+ if (classID == ClassID (Transform) && BeginsWith (attribute, "localEulerAngles"))
+ curveArray = &m_EulerEditorCurves;
+
+ // Find existing curve
+ FloatCurves::iterator i;
+ for (i=curveArray->begin();i != curveArray->end();i++)
+ {
+ if (i->classID == classID && i->path == path && i->attribute == attribute && i->script == script)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (curve == NULL)
+ {
+ if (i != curveArray->end ())
+ {
+ curveArray->erase(i);
+
+ // Modified euler angle curve, reload editor quaternion curves
+ if (classID == ClassID (Transform) && (BeginsWith (attribute, "localEulerAngles")) )
+ ReloadEditorQuaternionCurves(path);
+ // Modified quaternion curve, reload editor euler angle curves
+ else if (classID == ClassID (Transform) && BeginsWith (attribute, "m_LocalRotation"))
+ ReloadEditorEulerCurves(path);
+
+ if (syncEditorCurves)
+ SyncEditorCurves();
+ }
+ return;
+ }
+
+ // Add or replace the curve
+ AnimationCurve* comboCurve = i != curveArray->end() ? &i->curve : NULL;
+ if (comboCurve == NULL)
+ {
+ curveArray->push_back(FloatCurve());
+ curveArray->back().path = path;
+ curveArray->back().attribute = attribute;
+ curveArray->back().classID = classID;
+ curveArray->back().script = script;
+ curveArray->back().curve = *curve;
+ }
+ else
+ *comboCurve = *curve;
+
+ // Modified euler angle curve, reload editor quaternion curves
+ if (classID == ClassID (Transform) && (BeginsWith (attribute, "localEulerAngles")))
+ ReloadEditorQuaternionCurves(path);
+ // Modified quaternion curve, reload editor euler angle curves
+ else if (classID == ClassID (Transform) && BeginsWith (attribute, "m_LocalRotation"))
+ ReloadEditorEulerCurves(path);
+
+ SyncEditorCurves();
+}
+
+bool AnimationClip::GetEditorCurve (const string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* curve)
+{
+ GetEditorCurvesSync();
+
+ // Find existing curve
+ FloatCurves::iterator i;
+ for (i=m_EditorCurves.begin();i != m_EditorCurves.end();i++)
+ {
+ if (i->classID == classID && i->path == path && i->attribute == attribute && i->script == script)
+ {
+ if (curve != NULL)
+ *curve = i->curve;
+ return true;
+ }
+ }
+
+ // Find existing curve in euler editor curves array
+ for (i=m_EulerEditorCurves.begin();i != m_EulerEditorCurves.end();i++)
+ {
+ if (i->classID == classID && i->path == path && i->attribute == attribute && i->script == script)
+ {
+ if (curve != NULL)
+ *curve = i->curve;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+AnimationClip::FloatCurves& AnimationClip::GetEditorCurvesSync ()
+{
+ SET_ALLOC_OWNER(this);
+ if (m_EditorCurves.empty() && m_EulerEditorCurves.empty())
+ {
+ m_EulerEditorCurves.clear();
+ m_EditorCurves = m_FloatCurves;
+
+ for (QuaternionCurves::iterator i=m_RotationCurves.begin ();i != m_RotationCurves.end ();i++)
+ {
+ FloatCurve curve;
+ curve.path = i->path;
+ curve.classID = ClassID(Transform);
+ AssertMsg(i->curve.GetKeyCount() >= 2, "Key count: %d on curve '%s'", i->curve.GetKeyCount(), i->path.c_str());
+
+ // Create quaternion curves
+ int first = m_EditorCurves.size();
+ curve.attribute = "m_LocalRotation.x";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.y";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.z";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalRotation.w";
+ m_EditorCurves.push_back(curve);
+
+ AnimationCurve* curves[4] = { &m_EditorCurves[first].curve, &m_EditorCurves[first+1].curve, &m_EditorCurves[first+2].curve, &m_EditorCurves[first+3].curve };
+ ExpandQuaternionCurve(i->curve, curves);
+
+ // Create euler curves
+ first = m_EulerEditorCurves.size();
+ curve.attribute = "localEulerAngles.x";
+ m_EulerEditorCurves.push_back(curve);
+ curve.attribute = "localEulerAngles.y";
+ m_EulerEditorCurves.push_back(curve);
+ curve.attribute = "localEulerAngles.z";
+ m_EulerEditorCurves.push_back(curve);
+
+ AnimationCurve* eulerCurves[3] = { &m_EulerEditorCurves[first].curve, &m_EulerEditorCurves[first+1].curve, &m_EulerEditorCurves[first+2].curve };
+ QuaternionCurveToEulerCurve(i->curve, eulerCurves);
+ }
+
+ for (Vector3Curves::iterator i=m_PositionCurves.begin ();i != m_PositionCurves.end ();i++)
+ {
+ FloatCurve curve;
+ curve.path = i->path;
+ curve.classID = ClassID(Transform);
+
+ int first = m_EditorCurves.size();
+ curve.attribute = "m_LocalPosition.x";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalPosition.y";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalPosition.z";
+ m_EditorCurves.push_back(curve);
+
+ AnimationCurve* curves[3] = { &m_EditorCurves[first].curve, &m_EditorCurves[first+1].curve, &m_EditorCurves[first+2].curve };
+ ExpandVector3Curve(i->curve, curves);
+ }
+
+ // Create scale curves
+ for (Vector3Curves::iterator i=m_ScaleCurves.begin ();i != m_ScaleCurves.end ();i++)
+ {
+ FloatCurve curve;
+ curve.path = i->path;
+ curve.classID = ClassID(Transform);
+
+ int first = m_EditorCurves.size();
+ curve.attribute = "m_LocalScale.x";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalScale.y";
+ m_EditorCurves.push_back(curve);
+ curve.attribute = "m_LocalScale.z";
+ m_EditorCurves.push_back(curve);
+
+ AnimationCurve* curves[3] = { &m_EditorCurves[first].curve, &m_EditorCurves[first+1].curve, &m_EditorCurves[first+2].curve };
+ ExpandVector3Curve(i->curve, curves);
+ }
+ }
+ return m_EditorCurves;
+}
+
+
+
+void AnimationClip::SyncEditorCurves ()
+{
+ SET_ALLOC_OWNER(this);
+ m_RotationCurves.clear();
+ m_PositionCurves.clear();
+ m_ScaleCurves.clear();
+ m_FloatCurves.clear();
+
+ for (FloatCurves::iterator i=m_EditorCurves.begin ();i != m_EditorCurves.end ();i++)
+ {
+ SetCurve(i->path, i->classID, i->script, i->attribute, &i->curve, false);
+ }
+
+ ClipWasModifiedAndUpdateMuscleRange ();
+ SetDirty();
+
+}
+
+struct CurveHasAnimatorAttributePredicate
+{
+ CurveHasAnimatorAttributePredicate(UnityStr &attr) : m_Attribute(attr) {}
+
+ bool operator()(const AnimationClip::FloatCurve &curve)
+ {
+ return curve.attribute == m_Attribute && curve.classID == ClassID(Animator);
+ }
+
+ UnityStr& m_Attribute;
+};
+
+void AnimationClip::SyncMuscleCurvesBackwardCompatibility()
+{
+ for (FloatCurves::iterator i=m_EditorCurves.begin ();i != m_EditorCurves.end ();i++)
+ {
+ if(i->classID == ClassID(Animator))
+ {
+ // check if attribute is already in m_FloatCurves, if not add it.
+ if(std::find_if(m_FloatCurves.begin(),m_FloatCurves.end(), CurveHasAnimatorAttributePredicate( i->attribute )) == m_FloatCurves.end())
+ {
+ m_FloatCurves.push_back(FloatCurve());
+ m_FloatCurves.back().path = i->path;
+ m_FloatCurves.back().attribute = i->attribute;
+ m_FloatCurves.back().classID = i->classID;
+ m_FloatCurves.back().script = i->script;
+ m_FloatCurves.back().curve = i->curve;
+ }
+ }
+ }
+}
+
+struct EventSorter
+{
+ bool operator()( const AnimationEvent& ra, const AnimationEvent& rb ) const
+ {
+ return ra.time < rb.time;
+ }
+};
+
+static void SortEvents (AnimationClip::Events& events)
+{
+ std::sort (events.begin(), events.end(), EventSorter());
+}
+
+void AnimationClip::SetEvents (const AnimationEvent* events, int size, bool sort)
+{
+ m_Events.assign(events, events + size);
+ if (sort)
+ SortEvents(m_Events);
+ m_EditModeEvents = m_Events;
+
+ ClipWasModifiedAndUpdateMuscleRange ();
+
+ SetDirty();
+}
+
+void AnimationClip::ClearEvents ()
+{
+ m_Events.clear();
+ m_EditModeEvents.clear();
+
+ ClipWasModifiedAndUpdateMuscleRange ();
+
+ SetDirty();
+}
+
+void AnimationClip::CloneAdditionalEditorProperties (Object& src)
+{
+ Super::CloneAdditionalEditorProperties(src);
+ m_Events = static_cast<AnimationClip&> (src).m_Events;
+}
+
+void AnimationClip::RevertAllPlaymodeAnimationEvents ()
+{
+ vector<AnimationClip*> clips;
+ Object::FindObjectsOfType(&clips);
+ for (int i=0;i<clips.size();i++)
+ {
+ AnimationClip& clip = *clips[i];
+ if (clip.m_Events.size() != clip.m_EditModeEvents.size())
+ {
+ clip.m_Events = clip.m_EditModeEvents;
+ clip.ClipWasModifiedAndUpdateMuscleRange ();
+ }
+ }
+}
+
+#endif
+
+
+void AnimationClip::AddRuntimeEvent(AnimationEvent& event)
+{
+ Events::iterator i = lower_bound (m_Events.begin(), m_Events.end(), event);
+ m_Events.insert(i, event);
+ ClipWasModifiedAndUpdateMuscleRange ();
+
+ #if UNITY_EDITOR
+ if (!IsWorldPlaying())
+ {
+ ErrorString("Please use Editor.AnimationUtility to add persistent animation events to an animation clip");
+ }
+ #endif
+}
+
+#if UNITY_EDITOR
+bool AnimationClip::GetEditorPPtrCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, PPtrKeyframes* outKeyframes)
+{
+ PPtrCurves::iterator i;
+ for (i = m_PPtrCurves.begin(); i != m_PPtrCurves.end(); ++i)
+ {
+ if (i->path == path && i->classID == classID && i->script == script && i->attribute == attribute)
+ {
+ *outKeyframes = i->curve;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void AnimationClip::SetEditorPPtrCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, PPtrKeyframes* keyframes)
+{
+ SET_ALLOC_OWNER(this);
+
+ if (classID == -1)
+ {
+ ErrorString("Can't assign curve because the type does not inherit from Component.");
+ return;
+ }
+
+ // Find existing curve
+ PPtrCurves::iterator i;
+ for (i = m_PPtrCurves.begin(); i != m_PPtrCurves.end(); ++i)
+ {
+ if (i->path == path && i->classID == classID && i->script == script && i->attribute == attribute)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (keyframes == NULL)
+ {
+ if (i != m_PPtrCurves.end())
+ {
+ m_PPtrCurves.erase(i);
+ ClipWasModifiedAndUpdateMuscleRange ();
+ SetDirty();
+ }
+ return;
+ }
+
+ // Add or replace the curve
+ PPtrCurve* comboCurve = (i != m_PPtrCurves.end()) ? &(*i) : NULL;
+ if (comboCurve == NULL)
+ {
+ m_PPtrCurves.push_back(PPtrCurve());
+ comboCurve = &m_PPtrCurves.back();
+ }
+
+ comboCurve->path = path;
+ comboCurve->attribute = attribute;
+ comboCurve->classID = classID;
+ comboCurve->script = script;
+ comboCurve->curve = *keyframes;
+
+ ClipWasModifiedAndUpdateMuscleRange ();
+ SetDirty();
+}
+#endif
+
+bool AnimationClip::GetCurve (const string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* curve)
+{
+ AnimationCurve dummy0, dummy1, dummy2, dummy3;
+
+ // Get rotation curve
+ if (classID == ClassID(Transform) && BeginsWith (attribute, "m_LocalRotation"))
+ {
+ // Find existing curve
+ QuaternionCurves::iterator i;
+ for (i=m_RotationCurves.begin();i != m_RotationCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+
+ if (i == m_RotationCurves.end())
+ return false;
+
+ AnimationCurve* curves[4] = { &dummy0, &dummy1, &dummy2, &dummy3 };
+
+ char last = attribute[attribute.size()-1];
+ if (last == 'x')
+ curves[0] = curve;
+ else if (last == 'y')
+ curves[1] = curve;
+ else if (last == 'z')
+ curves[2] = curve;
+ else if (last == 'w')
+ curves[3] = curve;
+ else
+ {
+ ErrorString("Can't get curve because " + attribute + " is not a valid Transform property.");
+ return false;
+ }
+
+ ExpandQuaternionCurve(i->curve, curves);
+
+ return true;
+ }
+ // Get Local position / local scale curve
+ else if (classID == ClassID(Transform) && (BeginsWith (attribute, "m_LocalPosition") || BeginsWith (attribute, "m_LocalScale")))
+ {
+ Vector3Curves::iterator i;
+
+ if (BeginsWith (attribute, "m_LocalPosition"))
+ {
+ // Find existing curve
+ for (i=m_PositionCurves.begin();i != m_PositionCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+ if (i == m_PositionCurves.end())
+ return false;
+ }
+ else
+ {
+ // Find existing curve
+ for (i=m_ScaleCurves.begin();i != m_ScaleCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+ if (i == m_ScaleCurves.end())
+ return false;
+ }
+
+
+ AnimationCurve* curves[3] = { &dummy0, &dummy1, &dummy2 };
+
+ char last = attribute[attribute.size()-1];
+ if (last == 'x')
+ curves[0] = curve;
+ else if (last == 'y')
+ curves[1] = curve;
+ else if (last == 'z')
+ curves[2] = curve;
+ else
+ {
+ ErrorString("Can't get curve because " + attribute + " is not a valid Transform property.");
+ return false;
+ }
+
+ ExpandVector3Curve(i->curve, curves);
+
+ return true;
+ }
+ // Get any other type of curve
+ else
+ {
+ // Find existing curve
+ FloatCurves::iterator i;
+ for (i=m_FloatCurves.begin();i != m_FloatCurves.end();i++)
+ {
+ if (classID == i->classID && i->path == path && i->attribute == attribute && i->script == script)
+ break;
+ }
+
+ if (i == m_FloatCurves.end())
+ return false;
+
+ *curve = i->curve;
+ return true;
+ }
+}
+
+void AnimationClip::SetCurve (const string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* curve, bool syncEditorCurves)
+{
+ SET_ALLOC_OWNER(this);
+
+ #if UNITY_EDITOR
+ if (syncEditorCurves)
+ {
+ m_EditorCurves.clear();
+ m_EulerEditorCurves.clear();
+ }
+ #endif
+
+ if (classID == -1)
+ {
+ ErrorString("Can't assign curve because the type does not inherit from Component.");
+ return;
+ }
+
+ if (classID == ClassID(Transform) && (BeginsWith (attribute, "m_LocalRotation") || BeginsWith (attribute, "localRotation")))
+ {
+ // Find existing curve
+ QuaternionCurves::iterator i;
+ for (i=m_RotationCurves.begin();i != m_RotationCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (curve == NULL)
+ {
+ if (attribute == "m_LocalRotation" || attribute == "localRotation")
+ {
+ if (i != m_RotationCurves.end())
+ {
+ m_RotationCurves.erase(i);
+ ClipWasModified ();
+
+ SetDirty();
+ }
+ return;
+ }
+ else
+ {
+ ErrorString("Can't remove individual animation rotation curve " + attribute + " you must remove the entire animation curve with m_LocalRotation.");
+ return;
+ }
+ }
+
+ // Add the curve if it doesnt exist already
+ AnimationCurveQuat* comboCurve = i != m_RotationCurves.end() ? &i->curve : NULL;
+
+ if (comboCurve == NULL)
+ {
+ m_RotationCurves.push_back(QuaternionCurve());
+ m_RotationCurves.back().path = path;
+ comboCurve = &m_RotationCurves.back().curve;
+ }
+
+ // Combine the curve into a rotation curve
+ char last = attribute[attribute.size()-1];
+ if (last == 'x')
+ CombineCurve(*curve, 0, *comboCurve);
+ else if (last == 'y')
+ CombineCurve(*curve, 1, *comboCurve);
+ else if (last == 'z')
+ CombineCurve(*curve, 2, *comboCurve);
+ else if (last == 'w')
+ CombineCurve(*curve, 3, *comboCurve);
+ else
+ {
+ ErrorString("Can't assign curve because " + attribute + " is not a valid Transform property.");
+ }
+ }
+ else if (classID == ClassID(Transform) && (BeginsWith (attribute, "m_LocalPosition") || BeginsWith (attribute, "localPosition")))
+ {
+ // Find existing curve
+ Vector3Curves::iterator i;
+ for (i=m_PositionCurves.begin();i != m_PositionCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (curve == NULL)
+ {
+ if (attribute == "m_LocalPosition" || attribute == "localPosition")
+ {
+ if (i != m_PositionCurves.end())
+ {
+ m_PositionCurves.erase(i);
+ ClipWasModified ();
+ SetDirty();
+ }
+ return;
+ }
+ else
+ {
+ ErrorString("Can't remove individual position animation curve " + attribute + " you must remove the entire animation curve with m_LocalPosition.");
+ return;
+ }
+ }
+
+ // Add the curve if it doesnt exist already
+ AnimationCurveVec3* comboCurve = i != m_PositionCurves.end() ? &i->curve : NULL;
+ if (comboCurve == NULL)
+ {
+ m_PositionCurves.push_back(Vector3Curve());
+ m_PositionCurves.back().path = path;
+ comboCurve = &m_PositionCurves.back().curve;
+ }
+
+ // Combine the curve into a rotation curve
+ char last = attribute[attribute.size()-1];
+ if (last == 'x')
+ CombineCurve(*curve, 0, *comboCurve);
+ else if (last == 'y')
+ CombineCurve(*curve, 1, *comboCurve);
+ else if (last == 'z')
+ CombineCurve(*curve, 2, *comboCurve);
+ else
+ {
+ ErrorString("Can't assign curve because " + attribute + " is not a valid Transform property.");
+ }
+ }
+ else if (classID == ClassID(Transform) && (BeginsWith (attribute, "m_LocalScale") || BeginsWith (attribute, "localScale")))
+ {
+ // Find existing curve
+ Vector3Curves::iterator i;
+ for (i=m_ScaleCurves.begin();i != m_ScaleCurves.end();i++)
+ {
+ if (i->path == path)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (curve == NULL)
+ {
+ if (attribute == "m_LocalScale" || attribute == "localScale")
+ {
+ if (i != m_ScaleCurves.end())
+ {
+ m_ScaleCurves.erase(i);
+ ClipWasModified ();
+ SetDirty();
+ }
+ return;
+ }
+ else
+ {
+ ErrorString("Can't remove individual scale animation curve " + attribute + " you must remove the entire animation curve with m_LocalScale.");
+ return;
+ }
+ }
+
+ // Add the curve if it doesnt exist already
+ AnimationCurveVec3* comboCurve = i != m_ScaleCurves.end() ? &i->curve : NULL;
+ if (comboCurve == NULL)
+ {
+ m_ScaleCurves.push_back(Vector3Curve());
+ m_ScaleCurves.back().path = path;
+ comboCurve = &m_ScaleCurves.back().curve;
+ }
+
+ // Combine the curve into a rotation curve
+ char last = attribute[attribute.size()-1];
+ if (last == 'x')
+ CombineCurve(*curve, 0, *comboCurve);
+ else if (last == 'y')
+ CombineCurve(*curve, 1, *comboCurve);
+ else if (last == 'z')
+ CombineCurve(*curve, 2, *comboCurve);
+ else
+ {
+ ErrorString("Can't assign curve because " + attribute + " is not a valid Transform property.");
+ }
+ }
+ else
+ {
+ // Find existing curve
+ FloatCurves::iterator i;
+ for (i=m_FloatCurves.begin();i != m_FloatCurves.end();i++)
+ {
+ if (i->classID == classID && i->path == path && i->attribute == attribute && i->script == script)
+ break;
+ }
+
+ // Shall we remove a curve?
+ if (curve == NULL)
+ {
+ if (i != m_FloatCurves.end ())
+ {
+ m_FloatCurves.erase(i);
+ ClipWasModified ();
+ SetDirty();
+ }
+
+ return;
+ }
+
+ // Add or replace the curve
+ AnimationCurve* comboCurve = i != m_FloatCurves.end() ? &i->curve : NULL;
+ if (comboCurve == NULL)
+ {
+ m_FloatCurves.push_back(FloatCurve());
+ m_FloatCurves.back().path = path;
+ m_FloatCurves.back().attribute = attribute;
+ m_FloatCurves.back().classID = classID;
+ m_FloatCurves.back().script = script;
+ m_FloatCurves.back().curve = *curve;
+ }
+ else
+ *comboCurve = *curve;
+ }
+
+ ClipWasModified ();
+ SetDirty();
+}
+
+#if UNITY_EDITOR
+void AnimationClip::ConvertToNewCurveFormat (NewAnimationTrack& track, int classID, const string& path)
+{
+ NewAnimationTrack::Curves& curves = track.m_Curves;
+ for (NewAnimationTrack::Curves::iterator i=curves.begin();i!= curves.end();i++)
+ {
+ AnimationCurve& curve = i->curve;
+ SetCurve(path, classID, NULL, i->attributeName, &curve, true);
+ }
+}
+
+void AnimationClip::ConvertToNewCurveFormat ()
+{
+ for (ClassIDToTrack::iterator i=m_ClassIDToTrack.begin ();i != m_ClassIDToTrack.end ();i++)
+ {
+ NewAnimationTrack* track = dynamic_pptr_cast<NewAnimationTrack*> (i->second);
+ if (track)
+ ConvertToNewCurveFormat(*track, i->first, "");
+
+ SetDirty();
+ }
+
+ for (ChildTracks::iterator i=m_ChildTracks.begin ();i != m_ChildTracks.end ();i++)
+ {
+ NewAnimationTrack* track = dynamic_pptr_cast<NewAnimationTrack*> (i->track);
+ if (track)
+ ConvertToNewCurveFormat(*track, i->classID, i->path);
+
+ SetDirty();
+ }
+
+ // Just leak the actual animation track objects!
+ m_ClassIDToTrack.clear();
+ m_ChildTracks.clear();
+}
+#endif
+
+
+mecanim::animation::ClipMuscleConstant* AnimationClip::GetRuntimeAsset()
+{
+ #if UNITY_EDITOR
+ if (m_MuscleClip == NULL)
+ GenerateMuscleClip();
+ #endif
+
+ if (m_MuscleClip != 0 && m_MuscleClipSize != 0)
+ return m_MuscleClip;
+
+ return NULL;
+}
+
+void AnimationClip::GetStats(AnimationClipStats& stats)
+{
+ memset(&stats, 0, sizeof(stats));
+ stats.size = m_MuscleClipSize;
+
+ if (GetRuntimeAsset())
+ {
+ stats.totalCurves = 0;
+ for (int i=0;i<m_ClipBindingConstant.genericBindings.size();i++)
+ {
+ if (m_ClipBindingConstant.genericBindings[i].classID == ClassID(Transform))
+ {
+ switch (m_ClipBindingConstant.genericBindings[i].attribute)
+ {
+ case UnityEngine::Animation::kBindTransformPosition:
+ stats.positionCurves++;
+ break;
+ case UnityEngine::Animation::kBindTransformRotation:
+ stats.rotationCurves++;
+ break;
+ case UnityEngine::Animation::kBindTransformScale:
+ stats.scaleCurves++;
+ break;
+ }
+ }
+ else if (m_ClipBindingConstant.genericBindings[i].isPPtrCurve)
+ stats.pptrCurves++;
+ else if (IsMuscleBinding(m_ClipBindingConstant.genericBindings[i]))
+ stats.muscleCurves++;
+ else
+ stats.genericCurves++;
+
+ stats.totalCurves++;
+ }
+ }
+}
+
+bool AnimationClip::IsHumanMotion()
+{
+ return m_AnimationType == kHumanoid;
+}
+
+void AnimationClip::CleanupMecanimData()
+{
+ // Since the m_MuscleClip and all its data is placed on the m_Allocator, the destructor is not needed
+ m_MuscleClip = 0;
+ m_MuscleClipSize = 0;
+ m_ClipAllocator.Reset();
+
+ ///@TODO: Make destory function.
+ m_ClipBindingConstant.genericBindings.clear();
+ m_ClipBindingConstant.pptrCurveMapping.clear();
+}
+
+bool AnimationClip::IsAnimatorMotion()const
+{
+ return m_AnimationType == kHumanoid || m_AnimationType == kGeneric;
+}
+
+
+#if UNITY_EDITOR
+
+void AnimationClip::SetAnimationType(AnimationType type)
+{
+ m_AnimationType = type;
+}
+
+
+void AnimationClip::SetAnimationClipSettingsNoDirty(AnimationClipSettings &clipInfo)
+{
+ m_AnimationClipSettings = clipInfo;
+
+ if (GetRuntimeAsset())
+ PatchMuscleClipWithInfo (m_AnimationClipSettings, IsHumanMotion(), m_MuscleClip);
+}
+
+void AnimationClip::GenerateMuscleClip()
+{
+ CleanupMecanimData();
+
+ if (m_AnimationType == kLegacy)
+ return;
+
+ MecanimClipBuilder clipBuilder;
+ GenericAnimationBindingCache& binder = GetGenericAnimationBindingCache();
+
+ //// collect all curves, and count keys
+
+ // Position curves
+ for (Vector3Curves::iterator positionCurveIter = m_PositionCurves.begin (); positionCurveIter != m_PositionCurves.end (); positionCurveIter++)
+ AddPositionCurveToClipBuilder (positionCurveIter->curve, positionCurveIter->path, clipBuilder, m_UseHighQualityCurve);
+
+ // Rotation curves
+ for (QuaternionCurves::iterator rotationCurveIter = m_RotationCurves.begin (); rotationCurveIter != m_RotationCurves.end (); rotationCurveIter++)
+ AddRotationCurveToClipBuilder (rotationCurveIter->curve, rotationCurveIter->path, clipBuilder, m_UseHighQualityCurve);
+
+ // Scale curves
+ for (Vector3Curves::iterator scaleCurveIter = m_ScaleCurves.begin (); scaleCurveIter != m_ScaleCurves.end (); scaleCurveIter++)
+ AddScaleCurveToClipBuilder (scaleCurveIter->curve, scaleCurveIter->path, clipBuilder, m_UseHighQualityCurve);
+
+ // Dynamic binded curves
+ for (FloatCurves::iterator dynamicCurveIter = m_FloatCurves.begin(); dynamicCurveIter != m_FloatCurves.end (); dynamicCurveIter++)
+ {
+ GenericBinding binding;
+ binder.CreateGenericBinding (dynamicCurveIter->path, dynamicCurveIter->classID, dynamicCurveIter->script, dynamicCurveIter->attribute, false, binding);
+ AddGenericCurveToClipBuilder (dynamicCurveIter->curve, binding, clipBuilder, m_UseHighQualityCurve);
+ }
+
+ // PPtr curves
+ for (PPtrCurves::iterator pptrCurveIter = m_PPtrCurves.begin(); pptrCurveIter != m_PPtrCurves.end(); ++pptrCurveIter)
+ {
+ GenericBinding binding;
+ binder.CreateGenericBinding (pptrCurveIter->path, pptrCurveIter->classID, pptrCurveIter->script, pptrCurveIter->attribute, true, binding);
+ AddPPtrCurveToClipBuilder(pptrCurveIter->curve, binding, clipBuilder);
+ }
+
+ clipBuilder.hasAnimationEvents = HasAnimationEvents ();
+ clipBuilder.sampleRate = GetSampleRate();
+
+ if (!PrepareClipBuilder (clipBuilder))
+ return;
+
+ m_MuscleClip = BuildMuscleClip (clipBuilder, m_AnimationClipSettings, IsHumanMotion (), m_ClipBindingConstant, m_ClipAllocator);
+ if (m_MuscleClip)
+ {
+ BlobWrite::container_type blob;
+ BlobWrite blobWrite (blob, kNoTransferInstructionFlags, kBuildNoTargetPlatform);
+ blobWrite.Transfer( *m_MuscleClip, "Base");
+
+ m_MuscleClipSize = blob.size();
+ }
+}
+
+
+
+
+bool AnimationClip::ValidateIfRetargetable(bool showWarning)
+{
+ if(!IsAnimatorMotion())
+ {
+ if(showWarning)
+ WarningString (Format("Animation clip '%s' is not retargetable. Animation clips used within the Animator Controller need to have Muscle Definition set up in the Asset Importer Inspector", GetName()));
+ return false;
+ }
+ return true;
+}
+
+bool AnimationClip::IsLooping()
+{
+ return m_AnimationClipSettings.m_LoopTime;
+}
+
+
+float AnimationClip::GetAverageDuration()
+{
+ return m_AnimationClipSettings.m_StopTime - m_AnimationClipSettings.m_StartTime;
+}
+
+float AnimationClip::GetAverageAngularSpeed()
+{
+ return m_MuscleClip != 0 ? m_MuscleClip->m_AverageAngularSpeed : 0;
+}
+
+Vector3f AnimationClip::GetAverageSpeed()
+{
+ return m_MuscleClip != 0 ? float4ToVector3f(m_MuscleClip->m_AverageSpeed) : Vector3f::zero;
+}
+
+float AnimationClip::GetApparentSpeed()
+{
+ // approximation of equivalent rectilinear motion speed that conserves kinectic energy ... i'll work on something better
+ return Magnitude(GetAverageSpeed()) * (1 + pow(GetAverageAngularSpeed()/2,2));
+}
+
+#endif
+
+AnimationClip::~AnimationClip ()
+{
+ gDidModifyClipCallback (NULL, m_AnimationStates);
+ m_MuscleClip = 0;
+ NotifyObjectUsers(kDidModifyMotion);
+}
+
+pair<float, float> AnimationClip::GetRange ()
+{
+ pair<float, float> range = make_pair (std::numeric_limits<float>::infinity (), -std::numeric_limits<float>::infinity ());
+ if (range != m_CachedRange)
+ return m_CachedRange;
+ for (QuaternionCurves::iterator i=m_RotationCurves.begin ();i != m_RotationCurves.end ();i++)
+ {
+ pair<float, float> curRange = i->curve.GetRange ();
+ range.first = min (curRange.first, range.first);
+ range.second = max (curRange.second, range.second);
+ }
+
+ for (Vector3Curves::iterator i=m_PositionCurves.begin ();i != m_PositionCurves.end ();i++)
+ {
+ pair<float, float> curRange = i->curve.GetRange ();
+ range.first = min (curRange.first, range.first);
+ range.second = max (curRange.second, range.second);
+ }
+
+ for (Vector3Curves::iterator i=m_ScaleCurves.begin ();i != m_ScaleCurves.end ();i++)
+ {
+ pair<float, float> curRange = i->curve.GetRange ();
+ range.first = min (curRange.first, range.first);
+ range.second = max (curRange.second, range.second);
+ }
+
+ for (FloatCurves::iterator i=m_FloatCurves.begin ();i != m_FloatCurves.end ();i++)
+ {
+ pair<float, float> curRange = i->curve.GetRange ();
+ range.first = min (curRange.first, range.first);
+ range.second = max (curRange.second, range.second);
+ }
+
+ for (PPtrCurves::iterator i=m_PPtrCurves.begin ();i != m_PPtrCurves.end ();i++)
+ {
+ if (i->curve.empty())
+ continue;
+
+ range.first = min (i->curve.front().time, range.first);
+ range.second = max (i->curve.back().time + 1.0F / m_SampleRate, range.second);
+ }
+
+ #if UNITY_EDITOR
+ // get a valid range for muscle clip when importing
+ for (FloatCurves::iterator i=m_EditorCurves.begin ();i != m_EditorCurves.end ();i++)
+ {
+ pair<float, float> curRange = i->curve.GetRange ();
+ range.first = min (curRange.first, range.first);
+ range.second = max (curRange.second, range.second);
+ }
+ #endif
+
+ if (!m_Events.empty())
+ {
+ range.first = min (m_Events.front().time, range.first);
+ range.second = max (m_Events.back().time, range.second);
+ }
+
+ if (range.first == std::numeric_limits<float>::infinity() && range.second == -std::numeric_limits<float>::infinity())
+ {
+ // arbitrary range - the length shouldn't matter because it doesn't have any keys or events anyway
+ // TODO : do not allow to create clips without any keys or events or least it show a warning (LocomotionSystem creates such clip now)
+ range.first = 0;
+ range.second = 1;
+ }
+
+ m_CachedRange = range;
+ AssertIf(!IsFinite(m_CachedRange.first) || !IsFinite(m_CachedRange.second));
+
+ return m_CachedRange;
+}
+
+IMPLEMENT_CLASS_HAS_INIT (AnimationClip)
+IMPLEMENT_OBJECT_SERIALIZE (AnimationClip)
+
+void AnimationClip::InitializeClass ()
+{
+ // 2.6 beta -> 2.6 final compatibility
+ RegisterAllowNameConversion("AnimationClip", "m_UseCompression", "m_Compressed");
+ // 4.3
+ RegisterAllowNameConversion("AnimationClip", "m_MuscleClipInfo", "m_AnimationClipSettings");
+}
+
+#if UNITY_EDITOR
+class StripCurvesForMecanimClips
+{
+public:
+
+ AnimationClip* clip;
+ AnimationClip::QuaternionCurves rotationCurves;
+ AnimationClip::Vector3Curves positionCurves;
+ AnimationClip::Vector3Curves scaleCurves;
+ AnimationClip::FloatCurves floatCurves;
+ AnimationClip::PPtrCurves pptrCurves;
+ bool stripCurves;
+
+ StripCurvesForMecanimClips (AnimationClip& inputClip, bool inStripCurves)
+ {
+ if (inStripCurves)
+ {
+ clip = &inputClip;
+ rotationCurves.swap (clip->m_RotationCurves);
+ positionCurves.swap (clip->m_PositionCurves);
+ scaleCurves.swap (clip->m_ScaleCurves);
+ floatCurves.swap (clip->m_FloatCurves);
+ pptrCurves.swap (clip->m_PPtrCurves);
+ }
+ else
+ {
+ clip = NULL;
+ }
+ }
+
+ ~StripCurvesForMecanimClips ()
+ {
+ if (clip)
+ {
+ rotationCurves.swap (clip->m_RotationCurves);
+ positionCurves.swap (clip->m_PositionCurves);
+ scaleCurves.swap (clip->m_ScaleCurves);
+ floatCurves.swap (clip->m_FloatCurves);
+ pptrCurves.swap (clip->m_PPtrCurves);
+ }
+ }
+};
+#endif
+
+static void ConvertDeprecatedValueArrayConstantBindingToGenericBinding (const mecanim::animation::ClipMuscleConstant* muscleClip, UnityEngine::Animation::AnimationClipBindingConstant& bindings)
+{
+ if (muscleClip == NULL || muscleClip->m_Clip.IsNull() || muscleClip->m_Clip->m_DeprecatedBinding.IsNull())
+ return;
+
+ const mecanim::ValueArrayConstant& values = *muscleClip->m_Clip->m_DeprecatedBinding;
+
+ for (int i=0;i<values.m_Count;)
+ {
+ mecanim::uint32_t curveID = values.m_ValueArray[i].m_ID;
+ mecanim::uint32_t curveTypeID = values.m_ValueArray[i].m_TypeID;
+
+ bindings.genericBindings.push_back(GenericBinding());
+ GenericBinding& binding = bindings.genericBindings.back();
+
+ if(curveTypeID == mecanim::CRCKey(mecanim::ePositionX))
+ {
+ binding.path = curveID;
+ binding.attribute = UnityEngine::Animation::kBindTransformPosition;
+ binding.classID = ClassID(Transform);
+ i+=3;
+ }
+ else if(curveTypeID == mecanim::CRCKey(mecanim::eQuaternionX))
+ {
+ binding.path = curveID;
+ binding.attribute = UnityEngine::Animation::kBindTransformRotation;
+ binding.classID = ClassID(Transform);
+ i+=4;
+ }
+ else if(curveTypeID == mecanim::CRCKey(mecanim::eScaleX))
+ {
+ binding.path = curveID;
+ binding.attribute = UnityEngine::Animation::kBindTransformScale;
+ binding.classID = ClassID(Transform);
+ i+=3;
+ }
+ else
+ {
+ if(curveTypeID == mecanim::CRCKey(mecanim::ePositionY) || curveTypeID == mecanim::CRCKey(mecanim::ePositionZ) || curveTypeID == mecanim::CRCKey(mecanim::eQuaternionY) || curveTypeID == mecanim::CRCKey(mecanim::eQuaternionZ) || curveTypeID == mecanim::CRCKey(mecanim::eQuaternionW) || curveTypeID == mecanim::CRCKey(mecanim::eScaleY) || curveTypeID == mecanim::CRCKey(mecanim::eScaleZ))
+ {
+ AssertString("Invalid value array data");
+ }
+ //
+ binding.classID = ClassID(Animator);
+ binding.path = 0;
+ binding.attribute = curveID;
+
+ i++;
+ }
+ }
+}
+
+template<class TransferFunction>
+void AnimationClip::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.SetVersion (4);
+
+ TRANSFER_ENUM(m_AnimationType);
+
+ // Strip mecanim curve data for non-legacy clips
+ #if UNITY_EDITOR
+ StripCurvesForMecanimClips revert (*this, transfer.IsWritingGameReleaseData() && m_AnimationType != kLegacy);
+ #endif
+
+ // Backwards compatibility with ancient tracks
+ #if UNITY_EDITOR
+ if (transfer.IsOldVersion(2) || transfer.IsOldVersion(1))
+ {
+ transfer.Transfer (m_ClassIDToTrack, "m_ClassIDToTrack", kHideInEditorMask);
+ transfer.Transfer (m_ChildTracks, "m_ChildTracks", kHideInEditorMask);
+ }
+ #endif
+
+ // Rotation curves / potentially compressed
+ transfer.Transfer (m_Compressed, "m_Compressed", kNotEditableMask);
+ transfer.Transfer (m_UseHighQualityCurve, "m_UseHighQualityCurve", kNotEditableMask);
+ transfer.Align();
+
+ if(!m_Compressed)
+ {
+ transfer.Transfer (m_RotationCurves, "m_RotationCurves", kHideInEditorMask);
+ CompressedQuaternionCurves empty;
+ transfer.Transfer (empty, "m_CompressedRotationCurves", kHideInEditorMask);
+ }
+ else
+ {
+ QuaternionCurves empty;
+ transfer.Transfer (empty, "m_RotationCurves", kHideInEditorMask);
+
+ TRANSFER_WITH_CUSTOM_GET_SET (CompressedQuaternionCurves, "m_CompressedRotationCurves",
+ CompressCurves(value),
+ DecompressCurves(value),
+ kHideInEditorMask);
+ }
+
+ transfer.Transfer (m_PositionCurves, "m_PositionCurves", kHideInEditorMask);
+ transfer.Transfer (m_ScaleCurves, "m_ScaleCurves", kHideInEditorMask);
+ transfer.Transfer (m_FloatCurves, "m_FloatCurves", kHideInEditorMask);
+ transfer.Transfer (m_PPtrCurves, "m_PPtrCurves", kHideInEditorMask);
+ transfer.Transfer (m_SampleRate, "m_SampleRate");
+ transfer.Transfer (m_WrapMode, "m_WrapMode");
+ transfer.Transfer (m_Bounds, "m_Bounds");
+
+ if (transfer.IsSerializingForGameRelease())
+ {
+ TRANSFER(m_MuscleClipSize);
+
+ if(m_MuscleClip == 0)
+ m_ClipAllocator.Reserve(m_MuscleClipSize);
+
+ // Enforce that there is always a valid muscle clip when building for the player.
+ if (transfer.IsWritingGameReleaseData ())
+ GetRuntimeAsset ();
+
+ transfer.SetUserData(&m_ClipAllocator);
+ TRANSFER_NULLABLE(m_MuscleClip, mecanim::animation::ClipMuscleConstant);
+ TRANSFER(m_ClipBindingConstant);
+
+ if (transfer.IsReadingBackwardsCompatible())
+ ConvertDeprecatedValueArrayConstantBindingToGenericBinding (m_MuscleClip, m_ClipBindingConstant);
+ }
+
+
+ // Editor curves
+ #if UNITY_EDITOR
+ if (!transfer.IsSerializingForGameRelease())
+ {
+ transfer.Transfer (m_AnimationClipSettings, "m_AnimationClipSettings");
+ transfer.Transfer (m_EditorCurves, "m_EditorCurves", kHideInEditorMask);
+ transfer.Transfer (m_EulerEditorCurves, "m_EulerEditorCurves", kHideInEditorMask);
+ }
+ #endif
+
+ // Events
+ #if UNITY_EDITOR
+ transfer.Transfer (m_EditModeEvents, "m_Events", kHideInEditorMask);
+ if (transfer.IsReading())
+ m_Events = m_EditModeEvents;
+ #else
+ transfer.Transfer (m_Events, "m_Events", kHideInEditorMask);
+ #endif
+
+ #if UNITY_EDITOR
+ if (transfer.IsVersionSmallerOrEqual(3))
+ {
+ SyncMuscleCurvesBackwardCompatibility();
+ }
+ #endif
+}
+
+void AnimationClip::CompressCurves (CompressedQuaternionCurves& compressedRotationCurves)
+{
+ bool didShowError = false;
+ compressedRotationCurves.resize(m_RotationCurves.size());
+
+ for(int i=0;i<compressedRotationCurves.size();i++)
+ {
+ compressedRotationCurves[i].CompressQuatCurve(m_RotationCurves[i]);
+ if( m_RotationCurves[i].curve.GetKeyCount() > 0 && !didShowError)
+ {
+ if( m_RotationCurves[i].curve.GetKey(0).time < -kCurveTimeEpsilon)
+ {
+ LogStringObject(Format("Animation Clip %s contains negative time keys. This may cause your animation to look wrong, as negative time keys are not supported in compressed animation clips!",this->GetName()),this);
+ didShowError = true;
+ }
+ }
+ }
+}
+
+void AnimationClip::DecompressCurves (CompressedQuaternionCurves& compressedRotationCurves)
+{
+ SET_ALLOC_OWNER(this);
+ m_RotationCurves.resize(compressedRotationCurves.size());
+
+ for(int i=0;i<compressedRotationCurves.size();i++)
+ compressedRotationCurves[i].DecompressQuatCurve(m_RotationCurves[i]);
+}
+
+
+#if UNITY_EDITOR
+template<class TransferFunction>
+void AnimationClip::ChildTrack::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (path);
+ TRANSFER (classID);
+ TRANSFER (track);
+}
+#endif
+
+template<class TransferFunction>
+void AnimationClip::QuaternionCurve::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (curve);
+ TRANSFER (path);
+}
+
+template<class TransferFunction>
+void AnimationClip::Vector3Curve::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (curve);
+ TRANSFER (path);
+}
+
+template<class TransferFunction>
+void AnimationClip::FloatCurve::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (curve);
+ TRANSFER (attribute);
+ TRANSFER (path);
+ TRANSFER (classID);
+ TRANSFER (script);
+}
+
+template<class TransferFunction>
+void AnimationClip::PPtrCurve::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (curve);
+ TRANSFER (attribute);
+ TRANSFER (path);
+ TRANSFER (classID);
+ TRANSFER (script);
+}
+
+template<class TransferFunction>
+void PPtrKeyframe::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (time);
+ TRANSFER (value);
+}
+
+void AnimationClip::SetDidModifyClipCallback(DidModifyClipCallback* callback)
+{
+ gDidModifyClipCallback = callback;
+}
+
+void AnimationClip::AddRotationCurve (const AnimationCurveQuat& quat, const std::string& path)
+{
+ SET_ALLOC_OWNER(this);
+ m_RotationCurves.push_back(QuaternionCurve());
+ m_RotationCurves.back().curve = quat;
+ m_RotationCurves.back().path = path;
+}
+
+void AnimationClip::AddPositionCurve (const AnimationCurveVec3& quat, const std::string& path)
+{
+ SET_ALLOC_OWNER(this);
+ m_PositionCurves.push_back(Vector3Curve());
+ m_PositionCurves.back().curve = quat;
+ m_PositionCurves.back().path = path;
+}
+
+void AnimationClip::AddScaleCurve (const AnimationCurveVec3& quat, const std::string& path)
+{
+ SET_ALLOC_OWNER(this);
+ m_ScaleCurves.push_back(Vector3Curve());
+ m_ScaleCurves.back().curve = quat;
+ m_ScaleCurves.back().path = path;
+}
+
+void AnimationClip::AddFloatCurve (const AnimationCurve& curve, const std::string& path, int classID, const std::string& attribute)
+{
+ m_FloatCurves.push_back(FloatCurve());
+ FloatCurve& fc = m_FloatCurves.back();
+ fc.curve = curve;
+ fc.path = path;
+ fc.classID = classID;
+ fc.attribute = attribute;
+}
diff --git a/Runtime/Animation/AnimationClip.h b/Runtime/Animation/AnimationClip.h
new file mode 100644
index 0000000..1c4c1c2
--- /dev/null
+++ b/Runtime/Animation/AnimationClip.h
@@ -0,0 +1,346 @@
+#ifndef ANIMATIONCLIP_H
+#define ANIMATIONCLIP_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Serialize/SerializeTraits.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "AnimationEvent.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/mecanim/memory.h"
+#include "AnimationClipBindings.h"
+#include "PPtrKeyframes.h"
+
+#include "Motion.h"
+
+#if UNITY_EDITOR
+#include "AnimationClipSettings.h"
+#endif
+
+namespace mecanim
+{
+ namespace animation
+ {
+ struct ClipMuscleConstant;
+ struct ClipMuscleInput;
+ }
+}
+
+struct AnimationClipStats;
+
+namespace Unity { class GameObject; }
+using namespace Unity;
+
+class BaseAnimationTrack;
+class NewAnimationTrack;
+class MonoScript;
+class Animation;
+class AnimationState;
+class CompressedAnimationCurve;
+
+/*
+ TODO:
+ * We currently don't handle double cover operator automatically for rotation curves
+ * We are not synchronizing animation state cached range correctly
+ * GetCurve is not implemented yet
+*/
+
+class AnimationClip : public Motion
+{
+public:
+ struct QuaternionCurve
+ {
+ UnityStr path;
+ AnimationCurveQuat curve;
+ int hash;
+
+ QuaternionCurve () { hash = 0; }
+ void CopyWithoutCurve(QuaternionCurve& other) const
+ {
+ other.path = path;
+ other.hash = hash;
+ }
+
+ DECLARE_SERIALIZE (QuaternionCurve)
+ };
+
+ struct Vector3Curve
+ {
+ UnityStr path;
+ AnimationCurveVec3 curve;
+ int hash;
+
+ Vector3Curve () { hash = 0; }
+ void CopyWithoutCurve(Vector3Curve& other) const
+ {
+ other.path = path;
+ other.hash = hash;
+ }
+
+ DECLARE_SERIALIZE (Vector3Curve)
+ };
+
+public:
+
+
+ struct PPtrCurve
+ {
+ UnityStr path;
+ UnityStr attribute;
+ int classID;
+ MonoScriptPPtr script;
+ PPtrKeyframes curve;
+
+ PPtrCurve () { }
+
+ DECLARE_SERIALIZE (PPtrCurve)
+ };
+
+ struct FloatCurve
+ {
+ UnityStr path;
+ UnityStr attribute;
+ int classID;
+ MonoScriptPPtr script;
+ AnimationCurve curve;
+ int hash;
+
+ FloatCurve () { hash = 0; }
+ void CopyWithoutCurve(FloatCurve& other) const
+ {
+ other.path = path;
+ other.attribute = attribute;
+ other.classID = classID;
+ other.script = script;
+ other.hash = hash;
+ }
+
+ DECLARE_SERIALIZE (FloatCurve)
+ };
+
+ typedef UNITY_VECTOR(kMemAnimation, QuaternionCurve) QuaternionCurves;
+ typedef UNITY_VECTOR(kMemAnimation, CompressedAnimationCurve) CompressedQuaternionCurves;
+ typedef UNITY_VECTOR(kMemAnimation, Vector3Curve) Vector3Curves;
+ typedef UNITY_VECTOR(kMemAnimation, FloatCurve) FloatCurves;
+ typedef UNITY_VECTOR(kMemAnimation, PPtrCurve) PPtrCurves;
+ typedef UNITY_VECTOR(kMemAnimation, AnimationEvent) Events;
+
+ enum AnimationType
+ {
+ kLegacy = 1,
+ kGeneric = 2,
+ kHumanoid = 3
+ };
+
+
+
+ REGISTER_DERIVED_CLASS (AnimationClip, Motion)
+ DECLARE_OBJECT_SERIALIZE (AnimationClip)
+
+ static void InitializeClass ();
+ static void CleanupClass () { }
+
+ AnimationClip (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~AnimationClip(); declared-by-macro
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+ virtual void CheckConsistency();
+
+
+ /// Assigns curve to the curve defined by path, classID and attribute
+ /// If curve is null the exisiting curve will be removed.
+ void SetCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* curve, bool syncEditorCurves);
+ bool GetCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* outCurve);
+
+ void ClearCurves ();
+ void EnsureQuaternionContinuity ();
+
+ bool HasAnimationEvents ();
+ void FireAnimationEvents (float lastTime, float now, Unity::Component& source);
+
+ #if UNITY_EDITOR
+ void SetEditorCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, const AnimationCurve* curve, bool syncEditorCurves = true);
+ bool GetEditorCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, AnimationCurve* outCurve);
+ FloatCurves& GetEditorCurvesSync();
+
+ void SetEditorPPtrCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, PPtrKeyframes* keyframes);
+ bool GetEditorPPtrCurve (const std::string& path, int classID, MonoScriptPPtr script, const std::string& attribute, PPtrKeyframes* outKeyframes);
+ PPtrCurves& GetEditorPPtrCurves() { return m_PPtrCurves; }
+
+ // Callback for telling animation window when an animclip got reloaded
+ typedef void OnAnimationClipAwake(AnimationClip* clip);
+ static void SetOnAnimationClipAwake (OnAnimationClipAwake *callback);
+
+ void SyncEditorCurves();
+ void SyncMuscleCurvesBackwardCompatibility();
+
+ void SetEvents (const AnimationEvent* events, int size, bool sort = false);
+ void ClearEvents ();
+ FloatCurves& GetEulerEditorCurves() { return m_EulerEditorCurves; }
+ virtual void CloneAdditionalEditorProperties (Object& src);
+ FloatCurves& GetEditorCurvesNoConversion () { return m_EditorCurves; }
+
+
+ const AnimationClipSettings& GetAnimationClipSettings() const { return m_AnimationClipSettings; }
+ void SetAnimationClipSettingsNoDirty (AnimationClipSettings &clipInfo);
+ void SetAnimationClipSettings (const AnimationClipSettings&clipInfo) { m_AnimationClipSettings = clipInfo; SetDirty(); }
+
+ void GenerateMuscleClip();
+
+ void SetAnimationType (AnimationType type);
+
+
+ // overloads from Motion
+ virtual float GetAverageDuration();
+ virtual float GetAverageAngularSpeed();
+ virtual Vector3f GetAverageSpeed();
+ virtual float GetApparentSpeed();
+
+ virtual bool ValidateIfRetargetable(bool showWarning = true);
+ virtual bool IsLooping();
+
+ #endif
+
+ virtual bool IsAnimatorMotion()const;
+
+ // Returns the smallest and largest keyframe time of any channel in the animation
+ // if no keyframes are contained make_pair (infinity, -infinity) is returned
+ std::pair<float, float> GetRange ();
+
+ // Animation State support
+ typedef List< ListNode<AnimationState> > AnimationStateList;
+ typedef void DidModifyClipCallback(AnimationClip* clip, AnimationStateList& states);
+
+ static void SetDidModifyClipCallback(DidModifyClipCallback* callback);
+ void AddAnimationState(ListNode<AnimationState>& node) { m_AnimationStates.push_back(node); }
+
+ void SetSampleRate (float s);
+ float GetSampleRate () { return m_SampleRate; }
+
+ void SetCompressionEnabled (bool s) { m_Compressed = s; }
+ bool GetCompressionEnabled () { return m_Compressed; }
+#if UNITY_EDITOR
+ void SetUseHighQualityCurve (bool s) { m_UseHighQualityCurve = s; }
+ bool GetUseHighQualityCurve ()const { return m_UseHighQualityCurve; }
+#endif
+ void SetWrapMode (int wrap) { m_WrapMode = wrap; SetDirty (); }
+ int GetWrapMode () { return m_WrapMode; }
+
+ Events& GetEvents () { return m_Events; }
+ void AddRuntimeEvent (AnimationEvent& event);
+
+ void AddRotationCurve (const AnimationCurveQuat& quat, const std::string& path);
+ void AddPositionCurve (const AnimationCurveVec3& quat, const std::string& path);
+ void AddScaleCurve (const AnimationCurveVec3& quat, const std::string& path);
+ void AddFloatCurve (const AnimationCurve& curve, const std::string& path, int classID, const std::string& attribute);
+
+ QuaternionCurves& GetRotationCurves() { return m_RotationCurves; }
+ Vector3Curves& GetPositionCurves() { return m_PositionCurves; }
+ Vector3Curves& GetScaleCurves() { return m_ScaleCurves; }
+ FloatCurves& GetFloatCurves() { return m_FloatCurves; }
+
+ const QuaternionCurves& GetRotationCurves() const { return m_RotationCurves; }
+ const Vector3Curves& GetPositionCurves() const { return m_PositionCurves; }
+ const Vector3Curves& GetScaleCurves() const { return m_ScaleCurves; }
+ const FloatCurves& GetFloatCurves() const { return m_FloatCurves; }
+
+ void SetBounds(const AABB& bounds) { m_Bounds = bounds; SetDirty(); }
+ const AABB& GetBounds() const { return m_Bounds; }
+
+ static void RevertAllPlaymodeAnimationEvents ();
+
+ bool IsHumanMotion();
+
+ AnimationType GetAnimationType () const { return m_AnimationType; }
+
+
+ UnityEngine::Animation::AnimationClipBindingConstant* GetBindingConstant () { return &m_ClipBindingConstant; }
+ mecanim::animation::ClipMuscleConstant* GetRuntimeAsset();
+
+ void GetStats(AnimationClipStats& stats);
+
+ void ClipWasModifiedAndUpdateMuscleRange ();
+ void UpdateMuscleClipRange ();
+
+
+private:
+ void CompressCurves (CompressedQuaternionCurves& compressedRotationCurves);
+ void DecompressCurves (CompressedQuaternionCurves& compressedRotationCurves);
+
+ void ClipWasModified (bool cleanupMecanimData = true);
+
+ void ReloadEditorEulerCurves (const string& path);
+ void ReloadEditorQuaternionCurves (const string& path);
+
+ void ConvertToNewCurveFormat (NewAnimationTrack& curves, int classID, const std::string& path);
+ void ConvertToNewCurveFormat ();
+
+ void CleanupMecanimData();
+
+ mecanim::memory::ChainedAllocator m_ClipAllocator;
+
+private:
+ AnimationStateList m_AnimationStates;
+
+ float m_SampleRate;
+ bool m_Compressed;
+ bool m_UseHighQualityCurve;
+ int m_WrapMode;///< enum { Default = 0, Once = 1, Loop = 2, PingPong = 4, ClampForever = 8 }
+
+ QuaternionCurves m_RotationCurves;
+ Vector3Curves m_PositionCurves;
+ Vector3Curves m_ScaleCurves;
+ FloatCurves m_FloatCurves;
+ PPtrCurves m_PPtrCurves;
+ Events m_Events;
+ AnimationType m_AnimationType;
+
+#if UNITY_EDITOR
+ AnimationClipSettings m_AnimationClipSettings;
+#endif//#if UNITY_EDITOR
+
+ mecanim::animation::ClipMuscleConstant* m_MuscleClip;
+ mecanim::uint32_t m_MuscleClipSize;
+ UnityEngine::Animation::AnimationClipBindingConstant m_ClipBindingConstant;
+
+ /// TODO: Serialiaze and do not compute it at all on startup
+ std::pair<float, float> m_CachedRange;
+
+ AABB m_Bounds;
+
+ #if UNITY_EDITOR
+
+ // Keep a copy of events setup from edit mode so we can safely revert events after playmode
+ Events m_EditModeEvents;
+ FloatCurves m_EditorCurves;
+ FloatCurves m_EulerEditorCurves;
+
+ struct ChildTrack
+ {
+ UnityStr path;
+ int classID;
+ PPtr<BaseAnimationTrack> track;
+ DECLARE_SERIALIZE (ChildTrack)
+ };
+
+ typedef vector_map<SInt32, PPtr<BaseAnimationTrack> > ClassIDToTrack;
+ typedef ClassIDToTrack::iterator iterator;
+ typedef std::vector<ChildTrack> ChildTracks;
+ typedef ChildTracks::iterator child_iterator;
+
+ ClassIDToTrack m_ClassIDToTrack;
+ ChildTracks m_ChildTracks;
+
+ friend class StripCurvesForMecanimClips;
+
+ #endif
+
+ friend class AnimationManager;
+};
+
+typedef std::vector<PPtr<AnimationClip> > AnimationClipVector;
+
+#endif
diff --git a/Runtime/Animation/AnimationClipBindings.h b/Runtime/Animation/AnimationClipBindings.h
new file mode 100644
index 0000000..b91362c
--- /dev/null
+++ b/Runtime/Animation/AnimationClipBindings.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "Runtime/BaseClasses/BaseObject.h"
+
+typedef UInt32 BindingHash;
+
+namespace UnityEngine
+{
+namespace Animation
+{
+
+struct GenericBinding
+{
+ BindingHash path;
+ BindingHash attribute;
+ PPtr<Object> script;
+ UInt16 classID;
+ UInt8 customType;
+ UInt8 isPPtrCurve;
+
+ GenericBinding () : path (0), attribute(0), classID(0), customType(0), isPPtrCurve(0)
+ {
+
+ }
+
+
+ DECLARE_SERIALIZE (GenericBinding)
+};
+
+struct AnimationClipBindingConstant
+{
+ dynamic_array<GenericBinding> genericBindings;
+ dynamic_array<PPtr<Object> > pptrCurveMapping;
+
+ DECLARE_SERIALIZE (AnimationClipBindingConstant)
+};
+
+template<class TransferFunc> inline
+void GenericBinding::Transfer (TransferFunc& transfer)
+{
+ TRANSFER(path);
+ TRANSFER(attribute);
+ TRANSFER(script);
+ TRANSFER(classID);
+ TRANSFER(customType);
+ TRANSFER(isPPtrCurve);
+}
+
+template<class TransferFunc> inline
+void AnimationClipBindingConstant::Transfer (TransferFunc& transfer)
+{
+ TRANSFER(genericBindings);
+ TRANSFER(pptrCurveMapping);
+}
+
+inline bool operator < (const GenericBinding& lhs, const GenericBinding& rhs)
+{
+ // Transform components are sorted first by attribute, then by path.
+ // This is because when creating the ValueArrayConstant.
+ // We want scale curves at the end. This is because scale curves are most likely to not actually be changed
+ // Thus more likely to be culled away by the constant clip optimization code.
+ if (lhs.classID == ClassID (Transform) && rhs.classID == ClassID (Transform))
+ {
+ if (lhs.attribute != rhs.attribute)
+ return lhs.attribute < rhs.attribute;
+
+ return lhs.path < rhs.path;
+ }
+
+ // All transform bindings always come first
+ int lhsClassID = lhs.classID == ClassID (Transform) ? -1 : lhs.classID;
+ int rhsClassID = rhs.classID == ClassID (Transform) ? -1 : rhs.classID;
+
+ if (lhsClassID != rhsClassID)
+ return lhsClassID < rhsClassID;
+ else if (lhs.isPPtrCurve != rhs.isPPtrCurve)
+ return lhs.isPPtrCurve < rhs.isPPtrCurve;
+ else if (lhs.customType != rhs.customType)
+ return lhs.customType < rhs.customType;
+ else if (lhs.path != rhs.path)
+ return lhs.path < rhs.path;
+ else if (lhs.script != rhs.script)
+ return lhs.script < rhs.script;
+ else
+ return lhs.attribute < rhs.attribute;
+}
+
+}
+}
diff --git a/Runtime/Animation/AnimationClipSettings.h b/Runtime/Animation/AnimationClipSettings.h
new file mode 100644
index 0000000..c866b9f
--- /dev/null
+++ b/Runtime/Animation/AnimationClipSettings.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "Runtime/Misc/BuildSettings.h"
+
+struct AnimationClipSettings
+{
+ float m_StartTime;
+ float m_StopTime;
+ float m_OrientationOffsetY;
+ float m_Level;
+ float m_CycleOffset;
+
+ bool m_LoopTime;
+ bool m_LoopBlend;
+ bool m_LoopBlendOrientation;
+ bool m_LoopBlendPositionY;
+ bool m_LoopBlendPositionXZ;
+ bool m_KeepOriginalOrientation;
+ bool m_KeepOriginalPositionY;
+ bool m_KeepOriginalPositionXZ;
+ bool m_HeightFromFeet;
+ bool m_Mirror;
+
+ DEFINE_GET_TYPESTRING (MuscleClipInfo)
+
+ AnimationClipSettings()
+ {
+ m_StartTime = 0;
+ m_StopTime = 1;
+ m_OrientationOffsetY = 0;
+ m_Level = 0;
+ m_CycleOffset = 0;
+
+ m_LoopTime = false;
+ m_LoopBlend = false;
+ m_LoopBlendOrientation = false;
+ m_LoopBlendPositionY = false;
+ m_LoopBlendPositionXZ = false;
+ m_KeepOriginalOrientation = false;
+ m_KeepOriginalPositionY = true;
+ m_KeepOriginalPositionXZ = false;
+ m_HeightFromFeet = false;
+ m_Mirror = false;
+ }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer)
+ {
+ transfer.SetVersion(2);
+
+ TRANSFER(m_StartTime);
+ TRANSFER(m_StopTime);
+ TRANSFER(m_OrientationOffsetY);
+ TRANSFER(m_Level);
+ TRANSFER(m_CycleOffset);
+
+ TRANSFER(m_LoopTime);
+ TRANSFER(m_LoopBlend);
+
+ TRANSFER(m_LoopBlendOrientation);
+ TRANSFER(m_LoopBlendPositionY);
+ TRANSFER(m_LoopBlendPositionXZ);
+ TRANSFER(m_KeepOriginalOrientation);
+ TRANSFER(m_KeepOriginalPositionY);
+ TRANSFER(m_KeepOriginalPositionXZ);
+ TRANSFER(m_HeightFromFeet);
+ TRANSFER(m_Mirror);
+ transfer.Align();
+
+ // Backwards compatibility (4.3 introduced seperate loopTime / loopBlend)
+ if (transfer.IsVersionSmallerOrEqual(1))
+ m_LoopTime = m_LoopBlend;
+ }
+};
+
diff --git a/Runtime/Animation/AnimationClipStats.h b/Runtime/Animation/AnimationClipStats.h
new file mode 100644
index 0000000..a1a46ba
--- /dev/null
+++ b/Runtime/Animation/AnimationClipStats.h
@@ -0,0 +1,14 @@
+#pragma once
+
+// Must be kept in sync with AnimationClipStats in AnimationUtility.txt
+struct AnimationClipStats
+{
+ int size;
+ int positionCurves;
+ int rotationCurves;
+ int scaleCurves;
+ int muscleCurves;
+ int genericCurves;
+ int pptrCurves;
+ int totalCurves;
+}; \ No newline at end of file
diff --git a/Runtime/Animation/AnimationClipUtility.cpp b/Runtime/Animation/AnimationClipUtility.cpp
new file mode 100644
index 0000000..7ea1963
--- /dev/null
+++ b/Runtime/Animation/AnimationClipUtility.cpp
@@ -0,0 +1,73 @@
+#include "UnityPrefix.h"
+#include "AnimationClipUtility.h"
+#include "AnimationClip.h"
+#include "AnimationCurveUtility.h"
+
+template <class T>
+void EnsureLoopFrameContinuity (AnimationCurveTpl<T>& curve) {}
+
+template <>
+void EnsureLoopFrameContinuity<Quaternionf> (AnimationCurveTpl<Quaternionf>& curve)
+{
+ EnsureQuaternionContinuityLoopFrame(curve);
+}
+
+template <class U, class T, typename A>
+void ClipAnimations (const std::vector<T, A>& curves, float startTime, float endTime, float sampleRate, bool duplicateLastFrame, std::vector<T, A>& destinationCurves)
+{
+ for (typename std::vector<T, A>::const_iterator it = curves.begin(); it != curves.end(); ++it)
+ {
+ T newCurve;
+ AssertMsg(it->curve.GetKeyCount() >= 2, "Key count: %d on curve %s", it->curve.GetKeyCount(), it->path.c_str());
+
+ if (ClipAnimationCurve (it->curve, newCurve.curve, startTime, endTime))
+ {
+ it->CopyWithoutCurve(newCurve);
+
+ newCurve.curve.SetPostInfinity(kClamp);
+ newCurve.curve.SetPreInfinity(kClamp);
+
+ if (duplicateLastFrame)
+ {
+ AddLoopingFrame(newCurve.curve, endTime - startTime + 1.0f/sampleRate);
+ EnsureLoopFrameContinuity(newCurve.curve);
+ }
+
+ AssertMsg(newCurve.curve.GetKeyCount() >= 2, "Key count: %d on curve %s", newCurve.curve.GetKeyCount(), it->path.c_str());
+
+ destinationCurves.push_back(newCurve);
+ }
+ }
+}
+
+void ClipAnimation (AnimationClip& sourceClip, AnimationClip& destinationClip, float startTimeSeconds, float endTimeSeconds, bool duplicateLastFrame)
+{
+ if (startTimeSeconds > endTimeSeconds)
+ std::swap(endTimeSeconds, startTimeSeconds);
+
+ ClipAnimations<Quaternionf>(sourceClip.GetRotationCurves(), startTimeSeconds, endTimeSeconds, sourceClip.GetSampleRate(), duplicateLastFrame, destinationClip.GetRotationCurves());
+ ClipAnimations<Vector3f>(sourceClip.GetPositionCurves(), startTimeSeconds, endTimeSeconds, sourceClip.GetSampleRate(), duplicateLastFrame, destinationClip.GetPositionCurves());
+ ClipAnimations<Vector3f>(sourceClip.GetScaleCurves(), startTimeSeconds, endTimeSeconds, sourceClip.GetSampleRate(), duplicateLastFrame, destinationClip.GetScaleCurves());
+ ClipAnimations<float>(sourceClip.GetFloatCurves(), startTimeSeconds, endTimeSeconds, sourceClip.GetSampleRate(), duplicateLastFrame, destinationClip.GetFloatCurves());
+#if UNITY_EDITOR
+ ClipAnimations<float>(sourceClip.GetEditorCurvesNoConversion(), startTimeSeconds, endTimeSeconds, sourceClip.GetSampleRate(), duplicateLastFrame, destinationClip.GetEditorCurvesNoConversion());
+ AssertIf(sourceClip.GetEditorCurvesNoConversion().size() < destinationClip.GetEditorCurvesNoConversion().size());
+#endif // #if UNITY_EDITOR
+
+ AssertIf(sourceClip.GetRotationCurves().size() < destinationClip.GetRotationCurves().size());
+ AssertIf(sourceClip.GetPositionCurves().size() < destinationClip.GetPositionCurves().size());
+ AssertIf(sourceClip.GetScaleCurves().size() < destinationClip.GetScaleCurves().size());
+ AssertIf(sourceClip.GetFloatCurves().size() < destinationClip.GetFloatCurves().size());
+}
+
+
+void CopyAnimation (AnimationClip& sourceClip, AnimationClip& destinationClip)
+{
+ destinationClip.GetRotationCurves() = sourceClip.GetRotationCurves();
+ destinationClip.GetPositionCurves() = sourceClip.GetPositionCurves();
+ destinationClip.GetScaleCurves() = sourceClip.GetScaleCurves();
+ destinationClip.GetFloatCurves() = sourceClip.GetFloatCurves();
+#if UNITY_EDITOR
+ destinationClip.GetEditorCurvesNoConversion() = sourceClip.GetEditorCurvesNoConversion();
+#endif // #if UNITY_EDITOR
+}
diff --git a/Runtime/Animation/AnimationClipUtility.h b/Runtime/Animation/AnimationClipUtility.h
new file mode 100644
index 0000000..1e7bf5f
--- /dev/null
+++ b/Runtime/Animation/AnimationClipUtility.h
@@ -0,0 +1,6 @@
+#pragma once
+
+class AnimationClip;
+
+void ClipAnimation (AnimationClip& sourceClip, AnimationClip& destinationClip, float startTimeSeconds, float endTimeSeconds, bool duplicateLastFrame);
+void CopyAnimation (AnimationClip& sourceClip, AnimationClip& destinationClip);
diff --git a/Runtime/Animation/AnimationCurveUtility.cpp b/Runtime/Animation/AnimationCurveUtility.cpp
new file mode 100644
index 0000000..79c9b38
--- /dev/null
+++ b/Runtime/Animation/AnimationCurveUtility.cpp
@@ -0,0 +1,1009 @@
+#include "UnityPrefix.h"
+#include "AnimationCurveUtility.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Utilities/Utility.h"
+
+template<class T>
+T SafeDeltaDivide (T y, float x)
+{
+ if (Abs(x) > kCurveTimeEpsilon)
+ return y / x;
+ else
+ return Zero<T>();
+}
+
+
+
+
+template<class T>
+inline T HermiteInterpolateDerived (float t, T p0, T m0, T m1, T p1)
+{
+ float t2 = t * t;
+
+ float a = 6.0F * t2 - 6.0F * t;
+ float b = 3.0F * t2 - 4.0F * t + 1.0F;
+ float c = 3.0F * t2 - 2.0F * t;
+ float d = -6.0F * t2 + 6.0F * t;
+
+ return a * p0 + b * m0 + c * m1 + d * p1;
+}
+
+template<class T>
+void RecalculateSplineSlopeT(AnimationCurveTpl<T>& curve, int key, float bias = 0.0F);
+
+using namespace std;
+
+// TODO : maybe we can remove it?
+// this function is used by ImportFBX
+void EnsureQuaternionContinuity (AnimationCurve** curves)
+{
+ if (!curves[0] || !curves[1] || !curves[2] || !curves[3])
+ return;
+
+ int keyCount = curves[0]->GetKeyCount ();
+ if (keyCount != curves[1]->GetKeyCount () || keyCount != curves[2]->GetKeyCount () || keyCount != curves[3]->GetKeyCount ())
+ return;
+
+ if (keyCount == 0)
+ return;
+
+ Quaternionf last (curves[0]->GetKey (keyCount-1).value, curves[1]->GetKey (keyCount-1).value, curves[2]->GetKey (keyCount-1).value, curves[3]->GetKey (keyCount-1).value);
+ for (int i=0;i<keyCount;i++)
+ {
+ Quaternionf cur (curves[0]->GetKey (i).value, curves[1]->GetKey (i).value, curves[2]->GetKey (i).value, curves[3]->GetKey (i).value);
+ if (Dot (cur, last) < 0.0F)
+ cur = Quaternionf (-cur.x, -cur.y, -cur.z, -cur.w);
+ last = cur;
+ curves[0]->GetKey (i).value = cur.x;
+ curves[1]->GetKey (i).value = cur.y;
+ curves[2]->GetKey (i).value = cur.z;
+ curves[3]->GetKey (i).value = cur.w;
+ }
+
+ for (int j=0;j<4;j++)
+ {
+ for (int i=0;i<keyCount;i++)
+ RecalculateSplineSlopeT (*curves[j], i);
+ }
+}
+
+
+void ExpandQuaternionCurve (AnimationCurveQuat& quat, AnimationCurve* outCurves[4])
+{
+ int size = quat.GetKeyCount();
+
+ for (int c=0;c<4;c++)
+ outCurves[c]->ResizeUninitialized(size);
+
+ for (int i=0;i<size;i++)
+ {
+ AnimationCurve::Keyframe key;
+ const AnimationCurveQuat::Keyframe& src = quat.GetKey(i);
+ key.time = src.time;
+ for (int c=0;c<4;c++)
+ {
+ key.value = src.value[c];
+ key.inSlope = src.inSlope[c];
+ key.outSlope = src.outSlope[c];
+ outCurves[c]->GetKey(i) = key;
+ }
+ }
+
+ for (int c=0;c<4;c++)
+ {
+ outCurves[c]->SetPreInfinity(quat.GetPreInfinity());
+ outCurves[c]->SetPostInfinity(quat.GetPostInfinity());
+ outCurves[c]->InvalidateCache();
+ }
+}
+
+void ExpandVector3Curve (AnimationCurveVec3& inCurve, AnimationCurve* outCurves[3])
+{
+ int size = inCurve.GetKeyCount();
+
+ for (int c=0;c<3;c++)
+ outCurves[c]->ResizeUninitialized(size);
+
+ for (int i=0;i<size;i++)
+ {
+ AnimationCurve::Keyframe key;
+ const AnimationCurveVec3::Keyframe& src = inCurve.GetKey(i);
+ key.time = src.time;
+ for (int c=0;c<3;c++)
+ {
+ key.value = src.value[c];
+ key.inSlope = src.inSlope[c];
+ key.outSlope = src.outSlope[c];
+ outCurves[c]->GetKey (i) = key;
+ }
+ }
+
+ for (int c=0;c<3;c++)
+ {
+ outCurves[c]->SetPreInfinity(inCurve.GetPreInfinity());
+ outCurves[c]->SetPostInfinity(inCurve.GetPostInfinity());
+ outCurves[c]->InvalidateCache();
+ }
+}
+
+template<class T>
+int AddInbetweenKey (AnimationCurveTpl<T>& curve, float curveT)
+{
+ int index = curve.FindIndex (curveT);
+ if (index == -1)
+ return -1;
+ const KeyframeTpl<T>& lhs = curve.GetKey (index);
+ const KeyframeTpl<T>& rhs = curve.GetKey (min(index+1, curve.GetKeyCount()-1));
+
+ return curve.AddKey(CalculateInbetweenKey(lhs, rhs, curveT));
+}
+
+
+template<class T>
+KeyframeTpl<T> CalculateInbetweenKey(const AnimationCurveTpl<T>& curve, float curveT)
+{
+ int index = curve.FindIndex (curveT);
+ const typename AnimationCurveTpl<T>::Keyframe& lhs = curve.GetKey (index);
+ const typename AnimationCurveTpl<T>::Keyframe& rhs = curve.GetKey (index+1);
+ return CalculateInbetweenKey(lhs, rhs, curveT);
+}
+
+
+template<class T>
+KeyframeTpl<T> CalculateInbetweenKey(const KeyframeTpl<T>& lhs, const KeyframeTpl<T>& rhs, float curveT)
+{
+ typename AnimationCurveTpl<T>::Keyframe key;
+ float dx = rhs.time - lhs.time;
+
+ AssertIf(dx == 0.0F);
+
+ float t = (curveT - lhs.time) / dx;
+
+ if (t < -kCurveTimeEpsilon)
+ {
+ key = lhs;
+ key.time = curveT;
+ key.inSlope = Zero<T>();
+ key.outSlope = Zero<T>();
+
+ return key;
+ }
+ else if (t > 1.0F + kCurveTimeEpsilon)
+ {
+ key = rhs;
+ key.time = curveT;
+ key.inSlope = Zero<T>();
+ key.outSlope = Zero<T>();
+ return key;
+ }
+
+ T m1 = lhs.outSlope * dx;
+ T m2 = rhs.inSlope * dx;
+
+ // Calculate the slope at t. This is simply done by deriving the Hermite basis functions
+ // and feeding the derived hermite interpolator normal curve values
+ T slope = HermiteInterpolateDerived (t, lhs.value, m1, m2, rhs.value);
+ if (dx > 1.0F / MaxTan<float>())
+ slope /= dx;
+ else
+ slope = MaxTan<T> ();
+
+ HandleSteppedTangent(lhs, rhs, slope);
+
+ key.inSlope = slope;
+ key.outSlope = slope;
+
+ // the value of the key is just interpolated
+ key.time = curveT;
+ key.value = HermiteInterpolate (t, lhs.value, m1, m2, rhs.value);
+
+ HandleSteppedCurve(lhs, rhs, key.value);
+
+ AssertIf(!IsFinite(key.value));
+
+ return key;
+}
+
+void QuaternionCurveToEulerCurve (AnimationCurveQuat& quat, AnimationCurve* outCurves[3])
+{
+ int size = quat.GetKeyCount();
+
+ for (int c=0;c<3;c++)
+ outCurves[c]->ResizeUninitialized(size);
+
+ for (int i=0;i<size;i++)
+ {
+ AnimationCurve::Keyframe key;
+ const AnimationCurveQuat::Keyframe& src = quat.GetKey(i);
+ key.time = src.time;
+
+ float idt = i > 0 ? key.time - quat.GetKey(i-1).time : quat.GetKey(i+1).time - key.time;
+ float odt = i < size-1 ? quat.GetKey(i+1).time - key.time : key.time - quat.GetKey(i-1).time;
+
+ Quaternionf quat = src.value;
+ Quaternionf iquat = quat + src.inSlope * idt / 3;
+ Quaternionf oquat = quat + src.outSlope * odt / 3;
+
+ quat = NormalizeSafe(quat);
+ iquat = NormalizeSafe(iquat);
+ oquat = NormalizeSafe(oquat);
+
+ Vector3f euler = QuaternionToEuler(quat) * Rad2Deg(1.0F);
+ Vector3f ieuler = QuaternionToEuler(iquat) * Rad2Deg(1.0F);
+ Vector3f oeuler = QuaternionToEuler(oquat) * Rad2Deg(1.0F);
+
+ for (int c=0;c<3;c++)
+ {
+ ieuler[c] = Repeat(ieuler[c] - euler[c] + 180.0F, 360.0F) + euler[c] - 180.0F;
+ oeuler[c] = Repeat(oeuler[c] - euler[c] + 180.0F, 360.0F) + euler[c] - 180.0F;
+
+ key.value = euler[c];
+ key.inSlope = 3 * (ieuler[c] - euler[c]) / idt;
+ key.outSlope = 3 * (oeuler[c] - euler[c]) / odt;
+ outCurves[c]->GetKey (i) = key;
+ }
+ }
+
+ for (int c=0;c<3;c++)
+ {
+ outCurves[c]->SetPreInfinity(quat.GetPreInfinity());
+ outCurves[c]->SetPostInfinity(quat.GetPostInfinity());
+ outCurves[c]->InvalidateCache();
+ }
+}
+
+Quaternionf EvaluateQuaternionFromEulerCurves (const AnimationCurve& curveX, const AnimationCurve& curveY, const AnimationCurve& curveZ, float time)
+{
+ Vector3f euler;
+ euler.x = curveX.Evaluate(time);
+ euler.y = curveY.Evaluate(time);
+ euler.z = curveZ.Evaluate(time);
+ return EulerToQuaternion (euler * Deg2Rad(1.0F));
+}
+
+void EulerToQuaternionCurve (const AnimationCurve& curveX, const AnimationCurve& curveY, const AnimationCurve& curveZ, AnimationCurveQuat& collapsed)
+{
+ int size, foundIndex;
+
+ float errorDelta = 0.002F;
+
+ const AnimationCurve* curves[3] = { &curveX, &curveY, &curveZ };
+
+ // Create keyframes in collapsed array with filled out time values based on keyframes of all 3 curves
+ for (int c=0;c<3;c++)
+ {
+ const AnimationCurve& curve = *curves[c];
+ size = curve.GetKeyCount();
+ for (int i=0;i<size;i++)
+ {
+ const float srcTime = curve.GetKey(i).time;
+
+ // Just add keyframe if there are not enough keys in curve
+ ///@TODO: incorrect when one curve has only one key, because it will just call AddKey multiple times on the same key potentially.
+ /// Some other code further down is doing the same thing...
+ bool addKey = true;
+
+ if (collapsed.IsValid())
+ {
+ foundIndex = collapsed.FindIndex(srcTime);
+
+ bool addKey = foundIndex < 0;
+
+ if (!addKey)
+ {
+ // Do we have a key that is in the curve but not in the collapsed curve?
+ // We check keys on the left and on the right of the found key
+ addKey =
+ !CompareApproximately(srcTime, collapsed.GetKey(foundIndex).time, errorDelta) &&
+ (foundIndex + 1 < collapsed.GetKeyCount() || !CompareApproximately(srcTime, collapsed.GetKey(foundIndex + 1).time, errorDelta));
+ }
+ }
+
+ if (addKey)
+ {
+ KeyframeTpl<Quaternionf> dst;
+ dst.time = srcTime;
+ collapsed.AddKey(dst);
+ }
+ }
+ }
+
+ // Evaluate values at keys
+ size = collapsed.GetKeyCount();
+ for (int i=0; i<size; i++)
+ {
+ // This part would be semi incorrect if the euler curves didn't all have the same keyframes,
+ // But luckily the UI enforces them to always be together.
+ float time = collapsed.GetKey(i).time;
+ KeyframeTpl<Quaternionf>& dst = collapsed.GetKey(i);
+ dst.value = EvaluateQuaternionFromEulerCurves(curveX, curveY, curveZ, time);
+ }
+
+ // Determine tangents in quaternion space by sampling deltas
+ // TODO: Use better way of sampling tangents
+ for (int i=0; i<size-1; i++)
+ {
+ float lTime = collapsed.GetKey(i).time;
+ float rTime = collapsed.GetKey(i+1).time;
+
+ // Sample euler curves epsilon time efter left key and get quaternion
+ Quaternionf quat = EvaluateQuaternionFromEulerCurves(curveX, curveY, curveZ, lTime*0.999F + rTime * 0.001F);
+ KeyframeTpl<Quaternionf>& dst = collapsed.GetKey(i);
+ Quaternionf qDelta ((quat.x - dst.value.x) * 1000/(rTime-lTime),
+ (quat.y - dst.value.y) * 1000/(rTime-lTime),
+ (quat.z - dst.value.z) * 1000/(rTime-lTime),
+ (quat.w - dst.value.w) * 1000/(rTime-lTime));
+ dst.outSlope = qDelta;
+
+ // Sample euler curves epsilon time before right key and get quaternion
+ Quaternionf quat2 = EvaluateQuaternionFromEulerCurves(curveX, curveY, curveZ, lTime*0.001F + rTime * 0.999F);
+ KeyframeTpl<Quaternionf>& dst2 = collapsed.GetKey(i+1);
+ Quaternionf qDelta2 ((dst2.value.x - quat2.x) * 1000/(rTime-lTime),
+ (dst2.value.y - quat2.y) * 1000/(rTime-lTime),
+ (dst2.value.z - quat2.z) * 1000/(rTime-lTime),
+ (dst2.value.w - quat2.w) * 1000/(rTime-lTime));
+ dst2.inSlope = qDelta2;
+ }
+
+ collapsed.SetPreInfinity(curveX.GetPreInfinity());
+ collapsed.SetPostInfinity(curveX.GetPostInfinity());
+
+ collapsed.InvalidateCache();
+ EnsureQuaternionContinuityPreserveSlope(collapsed);
+}
+
+template<class T>
+void CombineCurve (const AnimationCurve& curve, int index, AnimationCurveTpl<T>& collapsed)
+{
+ int size, foundIndex;
+ if (index == 0)
+ {
+ collapsed.SetPreInfinity(curve.GetPreInfinity());
+ collapsed.SetPostInfinity(curve.GetPostInfinity());
+ }
+
+ // There is nothing in the collapsed curve yet
+ // We will build it from scratch
+ if (collapsed.GetKeyCount() == 0)
+ {
+ collapsed.ResizeUninitialized(curve.GetKeyCount());
+
+ size = collapsed.GetKeyCount();
+ for (int i=0;i<size;i++)
+ {
+ const AnimationCurve::Keyframe& src = curve.GetKey(i);
+ KeyframeTpl<T>& dst = collapsed.GetKey(i);
+
+ dst.time = src.time;
+
+ dst.value = Zero<T>();
+ dst.inSlope = Zero<T>();
+ dst.outSlope = Zero<T>();
+ // Write value into x,y,z,w axis based on index
+ dst.value[index] = src.value;
+ dst.inSlope[index] = src.inSlope;
+ dst.outSlope[index] = src.outSlope;
+ }
+
+ collapsed.InvalidateCache();
+
+ return;
+ }
+
+ // TODO : it has to be smaller than 0.002, because keyframes migth be closer to each other than that
+ // we need some more advance technique to get this errorDelta or smarter way to match keyframes between curves
+ float errorDelta = 0.000002F;
+ //float errorDelta = 0.002F;
+
+ AnimationCurve::Cache curveCache;
+ typename AnimationCurveTpl<T>::Cache collapseCache;
+
+ // Insert any new keys that are defined in the curve but are not in collapsed
+ size = curve.GetKeyCount();
+ for (int i=0;i<size;i++)
+ {
+ const AnimationCurve::Keyframe& src = curve.GetKey(i);
+ if (collapsed.IsValid())
+ {
+ foundIndex = collapsed.FindIndex(collapseCache, src.time);
+ const KeyframeTpl<T>& lhs = collapsed.GetKey (foundIndex);
+ collapseCache.index = foundIndex;
+ collapseCache.time = lhs.time;
+ const KeyframeTpl<T>& rhs = collapsed.GetKey (foundIndex+1);
+
+ // Do we have a key that is in the curve but not in the collapsed curve?
+ // -> Add an inbetween
+ if (!CompareApproximately(src.time, lhs.time, errorDelta) && !CompareApproximately(src.time, rhs.time, errorDelta))
+ {
+ collapsed.AddKey(CalculateInbetweenKey(lhs, rhs, src.time));
+ collapseCache.Invalidate();
+ }
+ }
+ else
+ {
+ // We need at least two keyframes, for the AddInBetween key function to work
+ AssertIf(collapsed.GetKeyCount () != 1);
+ KeyframeTpl<T> copyKey = collapsed.GetKey (0);
+ copyKey.time = src.time;
+ collapsed.AddKey(copyKey);
+ collapseCache.Invalidate();
+ }
+ }
+
+ // Go through the dst keys.
+ // Either copy from a key at the same time
+ // or Calculate inbetween
+ size = collapsed.GetKeyCount();
+ for (int i=0;i<size;i++)
+ {
+ KeyframeTpl<T>& dst = collapsed.GetKey(i);
+ KeyframeTpl<float> inbetween;
+
+ if (curve.IsValid())
+ {
+ foundIndex = curve.FindIndex(curveCache, dst.time);
+ const KeyframeTpl<float>& lhs = curve.GetKey (foundIndex);
+ curveCache.index = foundIndex;
+ curveCache.time = lhs.time;
+ const KeyframeTpl<float>& rhs = curve.GetKey (foundIndex+1);
+
+ if (CompareApproximately(dst.time, lhs.time, errorDelta))
+ inbetween = lhs;
+ else if (CompareApproximately(dst.time, rhs.time, errorDelta))
+ inbetween = rhs;
+ else
+ inbetween = CalculateInbetweenKey(lhs, rhs, dst.time);
+ }
+ else
+ {
+ inbetween.value = curve.GetKeyCount() == 1 ? curve.GetKey (0).value : 0.0F;
+ inbetween.inSlope = 0;
+ inbetween.outSlope = 0;
+ }
+
+ dst.value[index] = inbetween.value;
+ dst.inSlope[index] = inbetween.inSlope;
+ dst.outSlope[index] = inbetween.outSlope;
+ }
+
+ collapsed.InvalidateCache();
+}
+
+
+#define CLIPPING_EPSILON (1.0F / 1000.0F)
+
+template<class T>
+void ValidateCurve (AnimationCurveTpl<T>& curve)
+{
+ if (!curve.IsValid())
+ return;
+
+ // validating that time of keyframes is increasing
+ float t = -100000.0F;
+ for (typename AnimationCurveTpl<T>::iterator i=curve.begin();i!=curve.end();i++)
+ {
+ AssertMsg(t < i->time, "Key frame placement is not increasing" ); // Would love to be able to specify the model here
+ t = i->time;
+ }
+}
+
+template<class T>
+int FindClipKey (const AnimationCurveTpl<T>& curve, float time)
+{
+ const KeyframeTpl<T>* i = std::lower_bound (curve.begin (), curve.end (), time, KeyframeCompare());
+
+ if (i == curve.end())
+ {
+ return curve.GetKeyCount() - 1;
+ }
+ else
+ {
+ int indexH = distance (curve.begin (), i);
+ int indexL = max(indexH-1,0);
+
+ float diffH = fabs(curve.GetKey(indexH).time - time);
+ float diffL = fabs(curve.GetKey(indexL).time - time);
+
+ if(diffH < diffL)
+ {
+ return indexH;
+ }
+ else
+ {
+ return indexL;
+ }
+ }
+}
+
+template<class T>
+bool ClipAnimationCurve (const AnimationCurveTpl<T>& sourceCurve, AnimationCurveTpl<T>& curve, float begin, float end)
+{
+ AssertIf(begin > end);
+ dynamic_array<typename AnimationCurveTpl<T>::Keyframe> scratch;
+
+ if (!sourceCurve.IsValid ())
+ {
+ return false;
+ }
+
+ pair<float, float> range = sourceCurve.GetRange();
+
+ float offset = -begin;
+
+ begin = clamp(begin, range.first, range.second);
+ end = clamp(end, range.first, range.second);
+
+ // Contains no frames
+ if (CompareApproximately(begin, end, CLIPPING_EPSILON))
+ {
+ return false;
+ }
+
+ int firstIndex = FindClipKey(sourceCurve, begin);
+ int lastIndex = FindClipKey(sourceCurve, end);
+
+ // 2 for the possible interpolated ones and one for extra nicenecess because usually we might
+ // add an extra looping frame later on
+ scratch.reserve(std::max(lastIndex - firstIndex, 0) + 3);
+
+ if (CompareApproximately (begin, sourceCurve.GetKey(firstIndex).time, CLIPPING_EPSILON))
+ {
+ scratch.push_back(sourceCurve.GetKey(firstIndex));
+ firstIndex++;
+ }
+ else
+ {
+ scratch.push_back(CalculateInbetweenKey(sourceCurve, begin));
+ if(begin > sourceCurve.GetKey(firstIndex).time) firstIndex++;
+ }
+
+ if (CompareApproximately (end, sourceCurve.GetKey(lastIndex).time, CLIPPING_EPSILON))
+ {
+ scratch.push_back(sourceCurve.GetKey(lastIndex));
+ }
+ else
+ {
+ scratch.push_back(CalculateInbetweenKey(sourceCurve, end));
+ if(end > sourceCurve.GetKey(lastIndex).time) lastIndex++;
+ }
+
+ // Insert all inbetween keys
+ if (lastIndex > firstIndex)
+ scratch.insert(scratch.begin()+1, sourceCurve.begin() + firstIndex, sourceCurve.begin() + lastIndex);
+
+ // Zero base the clipped animation
+ for (unsigned int i=0;i<scratch.size();i++)
+ scratch[i].time += offset;
+
+ curve.Assign(scratch.begin(), scratch.end());
+ curve.InvalidateCache();
+
+ ValidateCurve(curve);
+
+ AssertMsg(curve.GetKeyCount() >= 2, "Key count: %d", curve.GetKeyCount());
+
+ return true;
+}
+
+void EnsureQuaternionContinuityPreserveSlope (AnimationCurveQuat& curve)
+{
+ if (!curve.IsValid())
+ return;
+
+ int keyCount = curve.GetKeyCount ();
+
+ Quaternionf last (curve.GetKey (keyCount-1).value);
+ for (int i=0;i<keyCount;i++)
+ {
+ Quaternionf cur (curve.GetKey (i).value);
+ if (Dot (cur, last) < 0.0F)
+ {
+ cur = Quaternionf (-cur.x, -cur.y, -cur.z, -cur.w);
+ curve.GetKey (i).value = cur;
+ curve.GetKey (i).inSlope = -curve.GetKey (i).inSlope;
+ curve.GetKey (i).outSlope = -curve.GetKey (i).outSlope;
+ }
+ last = cur;
+ }
+}
+
+void EnsureQuaternionContinuityAndRecalculateSlope (AnimationCurveQuat& curve)
+{
+ if (!curve.IsValid())
+ return;
+
+ int keyCount = curve.GetKeyCount ();
+
+ Quaternionf last (curve.GetKey (keyCount-1).value);
+ for (int i=0;i<keyCount;i++)
+ {
+ Quaternionf cur (curve.GetKey (i).value);
+ if (Dot (cur, last) < 0.0F)
+ cur = Quaternionf (-cur.x, -cur.y, -cur.z, -cur.w);
+ last = cur;
+ curve.GetKey (i).value = cur;
+ }
+
+ for (int i=0;i<keyCount;i++)
+ RecalculateSplineSlopeT (curve, i);
+}
+
+template<class T>
+void RecalculateSplineSlope (AnimationCurveTpl<T>& curve)
+{
+ for (int i=0;i<curve.GetKeyCount ();i++)
+ RecalculateSplineSlopeT (curve, i);
+}
+
+template<class T>
+void RecalculateSplineSlopeLinear (AnimationCurveTpl<T>& curve)
+{
+ if (curve.GetKeyCount () < 2)
+ return;
+
+ for (int i=0;i<curve.GetKeyCount () - 1;i++)
+ {
+ RecalculateSplineSlopeLinear( curve, i );
+ }
+}
+
+template<class T>
+void RecalculateSplineSlopeLinear (AnimationCurveTpl<T>& curve, int key)
+{
+ AssertIf(key < 0 || key >= curve.GetKeyCount() - 1);
+ if (curve.GetKeyCount () < 2)
+ return;
+
+ float dx = curve.GetKey (key).time - curve.GetKey (key+1).time;
+ T dy = curve.GetKey (key).value - curve.GetKey (key+1).value;
+ T m = dy / dx;
+ curve.GetKey (key).outSlope = m;
+ curve.GetKey (key+1).inSlope = m;
+}
+
+void RecalculateSplineSlope (AnimationCurveTpl<float>& curve, int key, float bias)
+{
+ RecalculateSplineSlopeT<float>(curve, key, bias);
+}
+
+template<class T>
+void RecalculateSplineSlopeT (AnimationCurveTpl<T>& curve, int key, float b)
+{
+ AssertIf(key < 0 || key >= curve.GetKeyCount());
+ if (curve.GetKeyCount () < 2)
+ return;
+
+ // First keyframe
+ // in and out slope are set to be the slope from this to the right key
+ if (key == 0)
+ {
+ float dx = curve.GetKey (1).time - curve.GetKey (0).time;
+ T dy = curve.GetKey (1).value - curve.GetKey (0).value;
+ T m = dy / dx;
+ curve.GetKey (key).inSlope = m; curve.GetKey (key).outSlope = m;
+ }
+ // last keyframe
+ // in and out slope are set to be the slope from this to the left key
+ else if (key == curve.GetKeyCount () - 1)
+ {
+ float dx = curve.GetKey (key).time - curve.GetKey (key-1).time;
+ T dy = curve.GetKey (key).value - curve.GetKey (key-1).value;
+ T m = dy / dx;
+ curve.GetKey (key).inSlope = m; curve.GetKey (key).outSlope = m;
+ }
+ // Keys are on the left and right
+ // Calculates the slopes from this key to the left key and the right key.
+ // Then blend between them using the bias
+ // A bias of zero doesn't bend in any direction
+ // a positive bias bends to the right
+ else
+ {
+ float dx1 = curve.GetKey (key).time - curve.GetKey (key-1).time;
+ T dy1 = curve.GetKey (key).value - curve.GetKey (key-1).value;
+
+ float dx2 = curve.GetKey (key+1).time - curve.GetKey (key).time;
+ T dy2 = curve.GetKey (key+1).value - curve.GetKey (key).value;
+
+ T m1 = SafeDeltaDivide(dy1, dx1);
+ T m2 = SafeDeltaDivide(dy2, dx2);
+
+ T m = (1.0F + b) * 0.5F * m1 + (1.0F - b) * 0.5F * m2;
+ curve.GetKey (key).inSlope = m; curve.GetKey (key).outSlope = m;
+ }
+
+ curve.InvalidateCache ();
+}
+
+
+template<class T>
+void RecalculateSplineSlopeLoop (AnimationCurveTpl<T>& curve, int key, float b)
+{
+ AssertIf(key < 0 || key >= curve.GetKeyCount());
+ if (curve.GetKeyCount () < 2)
+ return;
+
+ int keyPrev = key - 1;
+ int keyNext = key + 1;
+ if (key == 0)
+ keyPrev = curve.GetKeyCount() - 2;
+ else if (key+1 == curve.GetKeyCount())
+ keyNext = 1;
+ else
+ AssertString("Not supported");
+
+ // Keys are on the left and right
+ // Calculates the slopes from this key to the left key and the right key.
+ // Then blend between them using the bias
+ // A bias of zero doesn't bend in any direction
+ // a positive bias bends to the right
+ float dx1 = curve.GetKey (key).time - curve.GetKey (keyPrev).time;
+ T dy1 = curve.GetKey (key).value - curve.GetKey (keyPrev).value;
+
+ float dx2 = curve.GetKey (keyNext).time - curve.GetKey (key).time;
+ T dy2 = curve.GetKey (keyNext).value - curve.GetKey (key).value;
+
+ T m1 = SafeDeltaDivide(dy1, dx1);
+ T m2 = SafeDeltaDivide(dy2, dx2);
+
+ T m = (1.0F + b) * 0.5F * m1 + (1.0F - b) * 0.5F * m2;
+ curve.GetKey (key).inSlope = m; curve.GetKey (key).outSlope = m;
+
+ curve.InvalidateCache ();
+}
+
+
+template<class T>
+void AddLoopingFrame (AnimationCurveTpl<T>& curve, float time)
+{
+ if (!curve.IsValid())
+ return;
+
+ KeyframeTpl<T> key;
+ key.time = time;
+ key.value = curve.GetKey(0).value;
+ key.inSlope = curve.GetKey(0).outSlope;
+ key.outSlope = curve.GetKey(0).outSlope;
+
+ curve.AddKey(key);
+
+ RecalculateSplineSlopeLoop(curve, 0, 0);
+ RecalculateSplineSlopeLoop(curve, curve.GetKeyCount()-1, 0);
+}
+
+void EnsureQuaternionContinuityLoopFrame (AnimationCurveQuat& curve)
+{
+ if( curve.GetKeyCount () < 2 )
+ return;
+
+ int keyCount = curve.GetKeyCount ();
+
+ Quaternionf last (curve.GetKey (keyCount-2).value);
+ Quaternionf cur (curve.GetKey (keyCount-1).value);
+ if (Dot (cur, last) < 0.0F)
+ cur = Quaternionf (-cur.x, -cur.y, -cur.z, -cur.w);
+ curve.GetKey (keyCount-1).value = cur;
+
+ RecalculateSplineSlopeLoop(curve, keyCount-1, 0);
+}
+
+
+int AddKeySmoothTangents (AnimationCurve& curve, float time, float value)
+{
+ AnimationCurve::Keyframe key;
+ key.time = time;
+ key.value = value;
+ int index = curve.AddKey (key);
+ if (index == -1)
+ return -1;
+
+ // Recalculate spline slope of this and the two keyframes around us!
+ if (index > 0)
+ RecalculateSplineSlope (curve, index - 1, 0.0F);
+ RecalculateSplineSlope (curve, index, 0.0F);
+ if (index + 1 < curve.GetKeyCount ())
+ RecalculateSplineSlope (curve, index + 1, 0.0F);
+
+ return index;
+}
+
+
+
+template<class T>
+T InterpolateKeyframe (const KeyframeTpl<T>& lhs, const KeyframeTpl<T>& rhs, float curveT)
+{
+ float dx = rhs.time - lhs.time;
+ T m1;
+ T m2;
+ float t;
+
+ if (dx != 0.0F)
+ {
+ t = (curveT - lhs.time) / dx;
+ m1 = lhs.outSlope * dx;
+ m2 = rhs.inSlope * dx;
+ }
+ else
+ {
+ t = 0.0F;
+ m1 = Zero<T>();
+ m2 = Zero<T>();
+ }
+
+ return HermiteInterpolate (t, lhs.value, m1, m2, rhs.value);
+}
+
+int UpdateCurveKey (AnimationCurve& curve, int index, const AnimationCurve::Keyframe& value)
+{
+ float time = curve.GetKey(index).time;
+ if ((index-1 < 0 || index+1 < curve.GetKeyCount()) &&
+ time > curve.GetKey(index-1).time &&
+ time < curve.GetKey(index+1).time)
+ {
+ curve.GetKey(index) = value;
+ return index;
+ }
+ else
+ {
+ curve.RemoveKeys(curve.begin() + index, curve.begin() + index + 1);
+ return curve.AddKey(value);
+ }
+}
+
+int MoveCurveKey (AnimationCurve& curve, int index, AnimationCurve::Keyframe value)
+{
+ float time = curve.GetKey(index).time;
+
+ curve.RemoveKeys(curve.begin() + index, curve.begin() + index + 1);
+ int newCloseIndex = curve.FindIndex(value.time);
+
+ if (newCloseIndex >= 0)
+ {
+ Assert(curve.GetKeyCount() > 0);
+
+ // Too close to some keyframes -> Keep time of the old time value
+ if((newCloseIndex - 1 >= 0 && Abs(value.time - curve.GetKey(clamp(newCloseIndex-1, 0, curve.GetKeyCount()-1)).time) < kCurveTimeEpsilon) ||
+ Abs(value.time - curve.GetKey(clamp(newCloseIndex , 0, curve.GetKeyCount()-1)).time) < kCurveTimeEpsilon ||
+ (newCloseIndex + 1 < curve.GetKeyCount() && Abs(value.time - curve.GetKey(clamp(newCloseIndex+1, 0, curve.GetKeyCount()-1)).time) < kCurveTimeEpsilon) ||
+ Abs(value.time - curve.GetKey(curve.GetKeyCount()-1).time) < kCurveTimeEpsilon)
+ {
+ value.time = time;
+ }
+ }
+
+ return curve.AddKey(value);
+}
+
+// Calculates Hermite curve coefficients
+void HermiteCooficients (double t, double& a, double& b, double& c, double& d)
+{
+ double t2 = t * t;
+ double t3 = t2 * t;
+
+ a = 2.0F * t3 - 3.0F * t2 + 1.0F;
+ b = t3 - 2.0F * t2 + t;
+ c = t3 - t2;
+ d = -2.0F * t3 + 3.0F * t2;
+}
+
+namespace TToArray
+{
+ template <class T> float& Index(T& value, int index) { return value[index]; }
+ template <class T> float Index(const T& value, int index) { return value[index]; }
+
+ template <> float& Index<float>(float& value, int index)
+ {
+ AssertIf(index != 0);
+ return value;
+ }
+ template <> float Index<float>(const float& value, int index)
+ {
+ AssertIf(index != 0);
+ return value;
+ }
+
+ template <class T> int CoordinateCount();
+ template <> int CoordinateCount<float>() { return 1; }
+ template <> int CoordinateCount<Vector3f>() { return 3; }
+ template <> int CoordinateCount<Quaternionf>() { return 4; }
+}
+
+template <class T>
+void FitTangents(KeyframeTpl<T>& key0, KeyframeTpl<T>& key1, float time1, float time2, const T& value1, const T& value2)
+{
+ AssertIf(fabsf(time1) < std::numeric_limits<float>::epsilon());
+ AssertIf(fabsf(time2) < std::numeric_limits<float>::epsilon());
+
+ const float dt = key1.time - key0.time;
+
+ const int coordinateCount = TToArray::CoordinateCount<T>();
+
+ if (fabsf(dt) < std::numeric_limits<float>::epsilon())
+ {
+ for (int i = 0; i < coordinateCount; ++i)
+ {
+ TToArray::Index(key0.outSlope, i) = 0;
+ TToArray::Index(key1.inSlope, i) = 0;
+ }
+ }
+ else
+ {
+ // p0 and p1 for Hermite curve interpolation equation
+ const T p0 = key0.value;
+ const T p1 = key1.value;
+
+ // Hermite coefficients at points time1 and time2
+ double a1, b1, c1, d1;
+ double a2, b2, c2, d2;
+
+ // TODO : try using doubles, because it doesn't work well when p0==p1==v0==v1
+ HermiteCooficients(time1, a1, b1, c1, d1);
+ HermiteCooficients(time2, a2, b2, c2, d2);
+
+ for (int i = 0; i < coordinateCount; ++i)
+ {
+ // we need to solve these two equations in order to find m0 and m1
+ // b1 * m0 + c1 * m1 = v0 - a1 * p0 - d1 * p1;
+ // b2 * m0 + c2 * m1 = v1 - a2 * p0 - d2 * p1;
+
+ // c1, c2 is never equal 0, because time1 and time2 not equal to 0
+
+ // divide by c1 and c2
+ // b1 / c1 * m0 + m1 = (v0 - a1 * p0 - d1 * p1) / c1;
+ // b2 / c2 * m0 + m1 = (v1 - a2 * p0 - d2 * p1) / c2;
+
+ // subtract one from another
+ // b1 / c1 * m0 - b2 / c2 * m0 = (v0 - a1 * p0 - d1 * p1) / c1 - (v1 - a2 * p0 - d2 * p1) / c2;
+
+ // solve for m0
+ // (b1 / c1 - b2 / c2) * m0 = (v0 - a1 * p0 - d1 * p1) / c1 - (v1 - a2 * p0 - d2 * p1) / c2;
+
+ const double v0 = TToArray::Index(value1, i);
+ const double v1 = TToArray::Index(value2, i);
+ const double pp0 = TToArray::Index(p0, i);
+ const double pp1 = TToArray::Index(p1, i);
+
+ // calculate m0
+ const double m0 = ((v0 - a1 * pp0 - d1 * pp1) / c1 - (v1 - a2 * pp0 - d2 * pp1) / c2) / (b1 / c1 - b2 / c2);
+
+ // solve for m1 using m0
+ // c1 * m1 = p0 - a1 * p0 - d1 * p1 - b1 * m0;
+
+ // calculate m1
+ const double m1 = (v0 - a1 * pp0 - d1 * pp1 - b1 * m0) / c1;
+
+ TToArray::Index(key0.outSlope, i) = static_cast<float>(m0 / dt);
+ TToArray::Index(key1.inSlope, i) = static_cast<float>(m1 / dt);
+ }
+ }
+}
+
+
+// Instantiate templates
+template void RecalculateSplineSlope (AnimationCurveTpl<float>& curve);
+
+template bool ClipAnimationCurve (const AnimationCurveTpl<float>& sourceCurve, AnimationCurveTpl<float>& curve, float begin, float end);
+template bool ClipAnimationCurve (const AnimationCurveTpl<Quaternionf>& sourceCurve, AnimationCurveTpl<Quaternionf>& curve, float begin, float end);
+template bool ClipAnimationCurve (const AnimationCurveTpl<Vector3f>& sourceCurve, AnimationCurveTpl<Vector3f>& curve, float begin, float end);
+
+template void CombineCurve (const AnimationCurve& curve, int index, AnimationCurveTpl<Vector3f>& collapsed);
+template void CombineCurve (const AnimationCurve& curve, int index, AnimationCurveTpl<Quaternionf>& collapsed);
+
+template void AddLoopingFrame (AnimationCurveTpl<float>& curve, float time);
+template void AddLoopingFrame (AnimationCurveTpl<Quaternionf>& curve, float time);
+template void AddLoopingFrame (AnimationCurveTpl<Vector3f>& curve, float time);
+
+template void RecalculateSplineSlopeLoop (AnimationCurveTpl<float>& curve, int key, float b);
+template void RecalculateSplineSlopeLoop (AnimationCurveTpl<Quaternionf>& curve, int key, float b);
+template void RecalculateSplineSlopeLoop (AnimationCurveTpl<Vector3f>& curve, int key, float b);
+
+template void RecalculateSplineSlopeLinear (AnimationCurveTpl<float>& curve);
+template void RecalculateSplineSlopeLinear (AnimationCurveTpl<float>& curve, int key);
+
+template float InterpolateKeyframe (const KeyframeTpl<float>& lhs, const KeyframeTpl<float>& rhs, float curveT);
+template Vector3f InterpolateKeyframe (const KeyframeTpl<Vector3f>& lhs, const KeyframeTpl<Vector3f>& rhs, float curveT);
+template Quaternionf InterpolateKeyframe (const KeyframeTpl<Quaternionf>& lhs, const KeyframeTpl<Quaternionf>& rhs, float curveT);
+
+template void FitTangents(KeyframeTpl<float>& key0, KeyframeTpl<float>& key1, float time1, float time2, const float& value1, const float& value2);
+template void FitTangents(KeyframeTpl<Vector3f>& key0, KeyframeTpl<Vector3f>& key1, float time1, float time2, const Vector3f& value1, const Vector3f& value2);
+template void FitTangents(KeyframeTpl<Quaternionf>& key0, KeyframeTpl<Quaternionf>& key1, float time1, float time2, const Quaternionf& value1, const Quaternionf& value2);
+
diff --git a/Runtime/Animation/AnimationCurveUtility.h b/Runtime/Animation/AnimationCurveUtility.h
new file mode 100644
index 0000000..c464338
--- /dev/null
+++ b/Runtime/Animation/AnimationCurveUtility.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "Runtime/Math/AnimationCurve.h"
+
+
+void EulerToQuaternionCurve (const AnimationCurve& curveX, const AnimationCurve& curveY, const AnimationCurve& curveZ, AnimationCurveQuat& collapsed);
+void QuaternionCurveToEulerCurve (AnimationCurveQuat& quat, AnimationCurve* outCurves[3]);
+
+/// /curve/ is the input float curve
+/// /index/ is the component index in the collapsed curve (x,y,z,w -> usually between 0 and 3)
+/// /collapsed/ is the curve
+/// This functioncall is usually called as many times as there are components (for a quaternion curve 4 times, Vector3 curve 3 times)
+template<class T>
+void CombineCurve (const AnimationCurve& curve, int index, AnimationCurveTpl<T>& collapsed);
+
+void ExpandVector3Curve (AnimationCurveVec3& quat, AnimationCurve* outCurves[3]);
+void ExpandQuaternionCurve (AnimationCurveQuat& quat, AnimationCurve* outCurves[4]);
+
+template<class T>
+void AddLoopingFrame (AnimationCurveTpl<T>& curve, float time);
+
+template<class T>
+bool ClipAnimationCurve (const AnimationCurveTpl<T>& sourceCurve, AnimationCurveTpl<T>& curve, float begin, float end);
+
+int UpdateCurveKey (AnimationCurve& curve, int index, const AnimationCurve::Keyframe& value);
+int MoveCurveKey (AnimationCurve& curve, int index, AnimationCurve::Keyframe value);
+
+// Calculates the keyframes in/out slope based on the bias parameter creating a smooth spline.
+// a bias of zero is default.
+// a positive bias bends the curve to the next key.
+// a negative bias bends the curve to the previous key.
+void RecalculateSplineSlope (AnimationCurveTpl<float>& curve, int key, float bias = 0.0F);
+
+template<class T>
+T InterpolateKeyframe (const KeyframeTpl<T>& lhs, const KeyframeTpl<T>& rhs, float curveT);
+
+int AddInbetweenKey (AnimationCurve& curve, float curveT);
+
+template<class T>
+KeyframeTpl<T> CalculateInbetweenKey(const KeyframeTpl<T>& lhs, const KeyframeTpl<T>& rhs, float curveT);
+
+template<class T>
+KeyframeTpl<T> CalculateInbetweenKey(const AnimationCurveTpl<T>& curve, float curveT);
+
+int AddKeySmoothTangents (AnimationCurve& curve, float time, float value);
+
+template<class T>
+void RecalculateSplineSlope (AnimationCurveTpl<T>& curve);
+
+template<class T>
+void RecalculateSplineSlopeLinear (AnimationCurveTpl<T>& curve, int key);
+
+template<class T>
+void RecalculateSplineSlopeLinear (AnimationCurveTpl<T>& curve);
+
+void EnsureQuaternionContinuityLoopFrame (AnimationCurveQuat& curve);
+void EnsureQuaternionContinuityPreserveSlope (AnimationCurveQuat& curve);
+void EnsureQuaternionContinuityAndRecalculateSlope (AnimationCurveQuat& curve);
+
+template<class T>
+void RecalculateSplineSlopeLoop (AnimationCurveTpl<T>& curve, int key, float b);
+
+// Fits tangents key0.outSlope and key1.inSlope to the point value1 and value2
+// value1 and value2 - points to fit (at time1 and time2)
+template <class T>
+void FitTangents(KeyframeTpl<T>& key0, KeyframeTpl<T>& key1, float time1, float time2, const T& value1, const T& value2);
+
diff --git a/Runtime/Animation/AnimationEvent.cpp b/Runtime/Animation/AnimationEvent.cpp
new file mode 100644
index 0000000..2613d11
--- /dev/null
+++ b/Runtime/Animation/AnimationEvent.cpp
@@ -0,0 +1,202 @@
+#include "UnityPrefix.h"
+#include "AnimationEvent.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/Backend/ScriptingArguments.h"
+#include "Runtime/Scripting/CommonScriptingClasses.h"
+#include "Runtime/Scripting/ScriptingManager.h"
+#include "Runtime/Scripting/Backend/ScriptingBackendApi.h"
+#include "Runtime/Scripting/Scripting.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/ScriptingObjectWithIntPtrField.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+#if ENABLE_MONO
+#endif
+
+
+INSTANTIATE_TEMPLATE_TRANSFER_EXPORTED (AnimationEvent)
+
+template<class TransferFunction>
+void AnimationEvent::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (time);
+ TRANSFER (functionName);
+ transfer.Transfer (stringParameter, "data");
+ transfer.Transfer (objectReferenceParameter, "objectReferenceParameter");
+ transfer.Transfer (floatParameter, "floatParameter");
+ transfer.Transfer (intParameter, "intParameter");
+
+ TRANSFER (messageOptions);
+}
+
+#if ENABLE_SCRIPTING
+
+
+static ScriptingObjectPtr s_ManagedAnimationEvent;
+
+static bool SetupInvokeArgument(ScriptingMethodPtr method, AnimationEvent& event, ScriptingArguments& parameters)
+{
+ int argCount = scripting_method_get_argument_count(method, GetScriptingTypeRegistry());
+
+ // Fast path - method takes no arguments
+ if (argCount == 0)
+ return true;
+
+ if (argCount > 1)
+ return false;
+
+ ScriptingTypePtr typeOfFirstArgument = scripting_method_get_nth_argumenttype(method,0,GetScriptingTypeRegistry());
+
+ const CommonScriptingClasses& cc = GetScriptingManager().GetCommonClasses();
+
+ if (typeOfFirstArgument == cc.floatSingle)
+ {
+ parameters.AddFloat(event.floatParameter);
+ return true;
+ }
+
+ if (typeOfFirstArgument == cc.int_32)
+ {
+ parameters.AddInt(event.intParameter);
+ return true;
+ }
+
+ if (typeOfFirstArgument == cc.string)
+ {
+ parameters.AddString(event.stringParameter.c_str());
+ return true;
+ }
+
+ if (typeOfFirstArgument == cc.animationEvent)
+ {
+ ScriptingObjectWithIntPtrField<AnimationEvent> scriptingAnimationEvent = scripting_object_new(GetScriptingManager().GetCommonClasses().animationEvent);
+ scriptingAnimationEvent.SetPtr(&event);
+
+ s_ManagedAnimationEvent = scriptingAnimationEvent.object;
+ parameters.AddObject(scriptingAnimationEvent.object);
+ return true;
+ }
+
+ if (scripting_class_is_subclass_of(typeOfFirstArgument,cc.unityEngineObject))
+ {
+ parameters.AddObject(Scripting::ScriptingWrapperFor(event.objectReferenceParameter));
+ return true;
+ }
+
+ if (scripting_class_is_enum(typeOfFirstArgument))
+ {
+ parameters.AddInt(event.intParameter);
+ return true;
+ }
+
+ return false;
+}
+
+static void CleanupManagedAnimationEventIfRequired()
+{
+ if (s_ManagedAnimationEvent == SCRIPTING_NULL)
+ return;
+
+ AnimationEvent* nativeAnimationEvent = NULL;
+ MarshallNativeStructIntoManaged(nativeAnimationEvent, s_ManagedAnimationEvent);
+ s_ManagedAnimationEvent = SCRIPTING_NULL;
+}
+
+static bool FireEventTo(MonoBehaviour& behaviour, AnimationEvent& event, AnimationState* state)
+{
+ ScriptingObjectPtr instance = behaviour.GetInstance ();
+ if (instance == SCRIPTING_NULL)
+ return false;
+
+ ScriptingMethodPtr method = behaviour.FindMethod (event.functionName.c_str());
+ if (method == SCRIPTING_NULL)
+ return false;
+
+
+ ScriptingInvocation invocation(method);
+
+ if (!SetupInvokeArgument(method, event, invocation.Arguments()))
+ {
+ ErrorStringObject (Format ("Failed to call AnimationEvent %s of class %s.\nThe function must have either 0 or 1 parameters and the parameter can only be: string, float, int, enum, Object and AnimationEvent.", scripting_method_get_name (method), behaviour.GetScriptClassName ().c_str ()), &behaviour);
+ return true;
+ }
+
+ // Suppress immediate destruction during the event callback to disallow
+ // the object killing itself directly or indirectly in there (would do bad
+ // things to the still updating animation state).
+ const bool disableImmediateDestruction = IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1);
+ bool oldDisableImmediateDestruction = false;
+ if (disableImmediateDestruction)
+ {
+ oldDisableImmediateDestruction = GetDisableImmediateDestruction ();
+ SetDisableImmediateDestruction (true);
+ }
+
+ event.stateSender = state;
+
+ ScriptingExceptionPtr exception = NULL;
+ invocation.object = instance;
+ invocation.logException = true;
+ invocation.objectInstanceIDContextForException = behaviour.GetInstanceID();
+ ScriptingObjectPtr returnValue = invocation.Invoke();
+
+ if (disableImmediateDestruction)
+ SetDisableImmediateDestruction (oldDisableImmediateDestruction);
+
+ if (returnValue && exception == NULL)
+ behaviour.HandleCoroutineReturnValue (method, returnValue);
+
+ event.stateSender = NULL;
+ CleanupManagedAnimationEventIfRequired();
+
+ return true;
+}
+
+static bool EventRequiresReceiver(const AnimationEvent& event)
+{
+ return event.messageOptions == 0;
+}
+
+#endif
+
+bool FireEvent (AnimationEvent& event, AnimationState* state, Unity::Component& animation)
+{
+ #if ENABLE_SCRIPTING
+ GameObject& go = animation.GetGameObject();
+ if (!go.IsActive ())
+ return false;
+
+ bool sent = false;
+
+ for (int i=0;i<go.GetComponentCount ();i++)
+ {
+ if (go.GetComponentClassIDAtIndex (i) != ClassID (MonoBehaviour))
+ continue;
+
+ MonoBehaviour& behaviour = static_cast<MonoBehaviour&> (go.GetComponentAtIndex (i));
+ if (FireEventTo(behaviour,event,state))
+ sent = true;
+ }
+
+ if (DEPLOY_OPTIMIZED)
+ return true;
+
+ if (sent)
+ return true;
+
+ if (!EventRequiresReceiver(event))
+ return true;
+
+ std::string warning = event.functionName.empty()
+ ? Format ("'%s' AnimationEvent has no function name specified!", go.GetName())
+ : Format ("'%s' AnimationEvent '%s' has no receiver! Are you missing a component?", go.GetName(), event.functionName.c_str());
+
+ ErrorStringObject (warning.c_str(), animation.GetGameObjectPtr());
+ return true;
+ #else
+ return false;
+ #endif
+}
diff --git a/Runtime/Animation/AnimationEvent.h b/Runtime/Animation/AnimationEvent.h
new file mode 100644
index 0000000..541d719
--- /dev/null
+++ b/Runtime/Animation/AnimationEvent.h
@@ -0,0 +1,29 @@
+#ifndef ANIMATIONEVENT_H
+#define ANIMATIONEVENT_H
+
+#include "Runtime/BaseClasses/GameObject.h"
+class AnimationState;
+
+struct AnimationEvent
+{
+ DECLARE_SERIALIZE (AnimationEvent)
+
+ float time;
+ UnityStr functionName;
+ UnityStr stringParameter;
+ PPtr<Object> objectReferenceParameter;
+ float floatParameter;
+ int intParameter;
+
+ int messageOptions;
+ mutable AnimationState* stateSender;
+
+ AnimationEvent() { messageOptions = 0; stateSender = NULL; floatParameter = 0.0F; intParameter = 0; }
+
+ friend bool operator < (const AnimationEvent& lhs, const AnimationEvent& rhs) { return lhs.time < rhs.time; }
+};
+
+bool FireEvent (AnimationEvent& event, AnimationState* state, Unity::Component& animation);
+
+
+#endif
diff --git a/Runtime/Animation/AnimationManager.cpp b/Runtime/Animation/AnimationManager.cpp
new file mode 100644
index 0000000..c4c3e71
--- /dev/null
+++ b/Runtime/Animation/AnimationManager.cpp
@@ -0,0 +1,53 @@
+#include "UnityPrefix.h"
+#include "AnimationManager.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Animation.h"
+#include "AnimationState.h"
+#include "AnimationStateNetworkProvider.h"
+#include "Runtime/Core/Callbacks/PlayerLoopCallbacks.h"
+
+static AnimationManager* gAnimationManager = NULL;
+
+void AnimationManager::Update ()
+{
+ // Update animations
+ double time = GetCurTime();
+ AnimationList* animations = NULL;
+ if (GetTimeManager ().IsUsingFixedTimeStep ())
+ animations = &m_FixedAnimations;
+ else
+ animations = &m_Animations;
+
+ // Animation List node can be destroyed in UpdateAnimation, if somebody writes an AnimationEvent
+ // to do that. So we have to use SafeListIterator
+ SafeIterator<AnimationList> j (*animations);
+ while (j.Next())
+ {
+ Animation& animation = **j;
+ animation.UpdateAnimation(time);
+ }
+}
+
+void AnimationManager::InitializeClass ()
+{
+ Assert(gAnimationManager == NULL);
+ gAnimationManager = UNITY_NEW_AS_ROOT (AnimationManager, kMemAnimation, "AnimationManager", "");
+
+ REGISTER_PLAYERLOOP_CALL (LegacyFixedAnimationUpdate, GetAnimationManager().Update());
+ REGISTER_PLAYERLOOP_CALL (LegacyAnimationUpdate, GetAnimationManager().Update ());
+
+ InitializeAnimationStateNetworkProvider();
+}
+
+void AnimationManager::CleanupClass ()
+{
+ Assert(gAnimationManager != NULL);
+ UNITY_DELETE(gAnimationManager, kMemAnimation);
+
+ CleanupAnimationStateNetworkProvider();
+}
+
+AnimationManager& GetAnimationManager ()
+{
+ return *gAnimationManager;
+}
diff --git a/Runtime/Animation/AnimationManager.h b/Runtime/Animation/AnimationManager.h
new file mode 100644
index 0000000..51c9ff3
--- /dev/null
+++ b/Runtime/Animation/AnimationManager.h
@@ -0,0 +1,39 @@
+#ifndef ANIMATIONMANAGER_H
+#define ANIMATIONMANAGER_H
+
+#include "Runtime/Utilities/LinkedList.h"
+
+class Animation;
+
+class AnimationManager
+{
+ public:
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ /// Animates all registered objects according to the registered parameters
+ /// Removes Animations if the animated object or the track is gone or if the time
+ /// of the track end time of the animation is reached
+ void Update ();
+
+ public:
+
+ void AddDynamic(ListNode<Animation>& node) { m_Animations.push_back(node); }
+ void AddFixed (ListNode<Animation>& node) { m_FixedAnimations.push_back(node); }
+
+#if ENABLE_PROFILER
+ int GetUpdatedAnimationCount () { return m_Animations.size_slow() + m_FixedAnimations.size_slow(); }
+#endif
+
+ private:
+
+ typedef List< ListNode<Animation> > AnimationList;
+
+ AnimationList m_Animations;
+ AnimationList m_FixedAnimations;
+};
+
+AnimationManager& GetAnimationManager ();
+
+#endif
diff --git a/Runtime/Animation/AnimationModule.jam b/Runtime/Animation/AnimationModule.jam
new file mode 100644
index 0000000..45e282a
--- /dev/null
+++ b/Runtime/Animation/AnimationModule.jam
@@ -0,0 +1,169 @@
+rule AnimationModule_ReportCpp
+{
+ local animationSources =
+ AnimationModule.jam
+ AnimationModuleRegistration.cpp
+ AnimationStateNetworkProvider.cpp
+ AnimationStateNetworkProvider.h
+ Animation.cpp
+ Animation.h
+ AnimationBinder.cpp
+ AnimationBinder.h
+ AnimatorGenericBindings.cpp
+ AnimatorGenericBindings.h
+ GenericAnimationBindingCache.cpp
+ GenericAnimationBindingCache.h
+ AnimationSetBinding.cpp
+ AnimationSetBinding.h
+ AnimatorOverrideController.cpp
+ AnimatorOverrideController.h
+ AnimationClipBindings.h
+ AnimationClipSettings.h
+ Motion.cpp
+ Motion.h
+ AnimationClip.cpp
+ AnimationClip.h
+ AnimationClipUtility.cpp
+ AnimationClipUtility.h
+ AnimationCurveUtility.cpp
+ AnimationCurveUtility.h
+ AnimationEvent.cpp
+ AnimationEvent.h
+ AnimationManager.cpp
+ AnimationManager.h
+ AnimationState.cpp
+ AnimationState.h
+ BaseAnimationTrack.cpp
+ BaseAnimationTrack.h
+ BoundCurveDeprecated.h
+ BoundCurve.h
+ MecanimUtility.cpp
+ MecanimUtility.h
+ NewAnimationTrack.cpp
+ NewAnimationTrack.h
+ Avatar.cpp
+ Avatar.h
+ AvatarBuilder.cpp
+ AvatarBuilder.h
+ Animator.cpp
+ Animator.h
+ AnimatorManager.cpp
+ AnimatorManager.h
+ AvatarPlayback.cpp
+ AvatarPlayback.h
+ RuntimeAnimatorController.cpp
+ RuntimeAnimatorController.h
+ AnimatorController.cpp
+ AnimatorController.h
+ MecanimArraySerialization.h
+ AnimationUtility.cpp
+ AnimationUtility.h
+ StreamedClipBuilder.cpp
+ StreamedClipBuilder.h
+ DenseClipBuilder.cpp
+ DenseClipBuilder.h
+ MecanimClipBuilder.cpp
+ MecanimClipBuilder.h
+ AnimationClipSettings.h
+ CalculateAnimatorSkinMatrices.cpp
+ CalculateAnimatorSkinMatrices.h
+ CharacterTestFixture.h
+ MecanimAnimation.h
+ MecanimAnimation.cpp
+ OptimizeTransformHierarchy.cpp
+ OptimizeTransformHierarchyTests.cpp
+ OptimizeTransformHierarchy.h
+ ;
+
+ local mecanimSources =
+ bind.h
+ bitset.h
+ defs.h
+ memory.h
+ string.h
+ types.h
+ vector.h
+ animation/avatar.h
+ animation/avatar.cpp
+ animation/clipmuscle.h
+ animation/clipmuscle.cpp
+ animation/blendtree.h
+ animation/blendtree.cpp
+ animation/curvedata.h
+ animation/curvedata.cpp
+ animation/denseclip.h
+ animation/denseclip.cpp
+ animation/streamedclip.h
+ animation/streamedclip.cpp
+ animation/constantclip.h
+ animation/constantclip.cpp
+ animation/damp.h
+ animation/damp.cpp
+ # animation/poseblender.h
+ # animation/poseblender.cpp
+ generic/crc32.h
+ generic/stringtable.h
+ generic/stringtable.cpp
+ generic/typetraits.h
+ generic/valuearray.cpp
+ generic/valuearray.h
+ # graph/binarynode.h
+ # graph/factory.cpp
+ # graph/factory.h
+ # graph/genericnode.h
+ # graph/graph.cpp
+ # graph/graph.h
+ # graph/node.cpp
+ # graph/node.h
+ # graph/plug.h
+ # graph/plugbinder.cpp
+ # graph/plugbinder.h
+ # graph/quaternionnode.h
+ # graph/unarynode.h
+ # graph/xformnode.h
+ math/collider.h
+ math/axes.h
+ human/hand.cpp
+ human/hand.h
+ human/handle.h
+ human/human.cpp
+ human/human.h
+ skeleton/skeleton.cpp
+ skeleton/skeleton.h
+ statemachine/statemachine.cpp
+ statemachine/statemachine.h
+ ;
+
+ local modulesources =
+ Runtime/Animation/$(animationSources)
+ Runtime/mecanim/$(mecanimSources)
+ ;
+
+ return $(modulesources) ;
+}
+
+rule AnimationModule_ReportTxt
+{
+ return
+ Runtime/Animation/ScriptBindings/AnimatorOverrideControllerBindings.txt
+ Runtime/Animation/ScriptBindings/Animations.txt
+ Runtime/Animation/ScriptBindings/AnimatorBindings.txt
+ Runtime/Animation/ScriptBindings/AvatarBuilderBindings.txt
+ Runtime/Animation/ScriptBindings/RuntimeAnimatorControllerBindings.txt
+ Runtime/Animation/ScriptBindings/Avatar.txt
+ ;
+}
+
+## For Coexist with old system
+rule AnimationModule_ReportIncludes
+{
+
+}
+
+rule AnimationModule_Init
+{
+ OverrideModule Animation : GetModule_Cpp : byOverridingWithMethod : AnimationModule_ReportCpp ;
+ OverrideModule Animation : GetModule_Txt : byOverridingWithMethod : AnimationModule_ReportTxt ;
+}
+
+#RegisterModule Animation ;
diff --git a/Runtime/Animation/AnimationModuleRegistration.cpp b/Runtime/Animation/AnimationModuleRegistration.cpp
new file mode 100644
index 0000000..c95c4bd
--- /dev/null
+++ b/Runtime/Animation/AnimationModuleRegistration.cpp
@@ -0,0 +1,53 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ClassRegistration.h"
+#include "Runtime/Modules/ModuleRegistration.h"
+
+static void RegisterAnimationClasses (ClassRegistrationContext& context)
+{
+ REGISTER_CLASS (Animation)
+ REGISTER_CLASS (AnimationClip)
+ REGISTER_CLASS (RuntimeAnimatorController)
+ REGISTER_CLASS (AnimatorController)
+ REGISTER_CLASS (Animator)
+ REGISTER_CLASS (Avatar)
+ REGISTER_CLASS (Motion)
+#if !UNITY_WEBGL
+ REGISTER_CLASS (AnimatorOverrideController)
+#endif
+#if UNITY_EDITOR
+ ///@TODO: Lets remove those. It's been deprecated since Unity 1.6
+ REGISTER_CLASS (BaseAnimationTrack)
+ REGISTER_CLASS (NewAnimationTrack)
+#endif
+}
+
+#if ENABLE_MONO || UNITY_WINRT
+void ExportRuntimeAnimatorControllerBindings();
+void ExportAnimatorBindings();
+void ExportAnimations();
+void ExportAvatarBuilderBindings ();
+void ExportAvatar ();
+void ExportAnimatorOverrideControllerBindings();
+
+static void RegisterAnimationICallModule ()
+{
+#if !INTERNAL_CALL_STRIPPING
+ ExportRuntimeAnimatorControllerBindings ();
+ ExportAnimatorBindings();
+ ExportAnimations ();
+ ExportAvatarBuilderBindings ();
+ ExportAvatar ();
+ ExportAnimatorOverrideControllerBindings ();
+#endif
+}
+#endif
+
+extern "C" EXPORT_MODULE void RegisterModule_Animation ()
+{
+ ModuleRegistrationInfo info;
+ info.registerClassesCallback = &RegisterAnimationClasses;
+#if ENABLE_MONO || UNITY_WINRT
+ info.registerIcallsCallback = &RegisterAnimationICallModule;
+#endif
+ RegisterModuleInfo (info);
+}
diff --git a/Runtime/Animation/AnimationSetBinding.cpp b/Runtime/Animation/AnimationSetBinding.cpp
new file mode 100644
index 0000000..234f64c
--- /dev/null
+++ b/Runtime/Animation/AnimationSetBinding.cpp
@@ -0,0 +1,563 @@
+#include "UnityPrefix.h"
+#include "AnimationSetBinding.h"
+#include "RuntimeAnimatorController.h"
+#include "AnimationClipBindings.h"
+#include "GenericAnimationBindingCache.h"
+#include "AnimationClip.h"
+#include "Runtime/mecanim/generic/stringtable.h"
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/mecanim/animation/curvedata.h"
+
+namespace UnityEngine
+{
+namespace Animation
+{
+static mecanim::ValueArrayConstant* CreateBindingValueArrayConstant (const GenericBinding* genericBindings, size_t genericBindingsSize, const GenericBinding* genericPPtrBindings, size_t genericPPtrBindingsSize, const TransformBinding* transformBindings, size_t transformBindingsSize, mecanim::memory::Allocator& alloc);
+static void CombineUniqueGeneric (AnimationClipBindingConstant** constants, mecanim::animation::AnimationSet::Clip** animationSetClips, size_t clipCount, dynamic_array<TransformBinding>& outputCombinedTransformBinding, int& outputNonConstantTransformCount, dynamic_array<GenericBinding>& outputCombinedGenericBinding, dynamic_array<GenericBinding>& outputCombinedGenericPPtrBinding, dynamic_array<float>& constantValues, mecanim::memory::Allocator& alloc);
+static void BindAdditionalCurves(mecanim::ValueArrayConstant const &valueConstant, dynamic_array<GenericBinding> const& genericBindings, size_t bindingOffset, mecanim::int32_t *additionalIndexArray);
+static void BindSkeletonMask(mecanim::ValueArrayConstant const &valueConstant, mecanim::skeleton::SkeletonMask const &skeletonMask, mecanim::ValueArrayMask &mask);
+
+
+bool IsPPtrAnimation (const GenericBinding& binding)
+{
+ return binding.isPPtrCurve;
+}
+
+void DestroyAnimationSetBindings (AnimationSetBindings* bindings, mecanim::memory::Allocator& allocator)
+{
+ if (bindings != NULL)
+ {
+ allocator.Deallocate (bindings->genericBindings);
+ allocator.Deallocate (bindings->genericPPtrBindings);
+ allocator.Deallocate (bindings->transformBindings);
+ DestroyAnimationSet (bindings->animationSet, allocator);
+ allocator.Deallocate (bindings);
+ }
+}
+
+static void BindGravityWeight (mecanim::animation::AnimationSet* animationSet, const mecanim::ValueArrayConstant* dynamicValuesConstant)
+{
+ animationSet->m_GravityWeightIndex = mecanim::FindValueIndex(dynamicValuesConstant, CRCKey(mecanim::eGravityWeight));
+ if (animationSet->m_GravityWeightIndex != -1)
+ animationSet->m_GravityWeightIndex = dynamicValuesConstant->m_ValueArray[animationSet->m_GravityWeightIndex].m_Index;
+}
+
+AnimationSetBindings* CreateAnimationSetBindings (mecanim::animation::ControllerConstant const* controller, AnimationClipVector const& clips, mecanim::memory::Allocator& allocator)
+{
+ if (controller == NULL)
+ return NULL;
+
+ SETPROFILERLABEL(AnimationSetBindings);
+
+
+ size_t clipCount = clips.size();
+
+ mecanim::animation::AnimationSet::Clip** animationSetClips;
+ ALLOC_TEMP(animationSetClips, mecanim::animation::AnimationSet::Clip*, clipCount);
+
+ AnimationClipBindingConstant** animationClipBindings;
+ ALLOC_TEMP(animationClipBindings, AnimationClipBindingConstant*, clipCount);
+
+ mecanim::animation::ClipMuscleConstant** clipConstants;
+ ALLOC_TEMP(clipConstants, mecanim::animation::ClipMuscleConstant*, clipCount);
+
+
+ // Create Animation Set
+ mecanim::animation::AnimationSet* animationSet = CreateAnimationSet(controller, allocator);
+ animationSet->m_IntegerRemapStride = sizeof(PPtr<Object>);
+
+
+ size_t clipArrayIndex = 0;
+ for(int layerIter=0; layerIter<animationSet->m_LayerCount; layerIter++)
+ {
+ for(int clipIter=0; clipIter < animationSet->m_ClipPerLayer[layerIter]; clipIter++)
+ {
+ AnimationClip* animationClip = clips[clipArrayIndex];
+ mecanim::animation::ClipMuscleConstant* clipConstant = animationClip != NULL ? animationClip->GetRuntimeAsset() : 0;
+
+ mecanim::animation::AnimationSet::Clip& setClip = animationSet->m_ClipConstant[layerIter][clipIter];
+ setClip.m_Clip = clipConstant;
+ setClip.m_ClipIndex = clipArrayIndex;
+
+ if (clipConstant != NULL)
+ {
+ animationSetClips[clipArrayIndex] = &setClip;
+ animationClipBindings[clipArrayIndex] = animationClip->GetBindingConstant();
+
+ // Setup pptrCurve mapping (mecanim accesses it through m_IntegerRemap & m_IntegerRemapStride)
+ setClip.m_Bindings.m_IntegerRemap = reinterpret_cast<mecanim::int32_t*> (animationClipBindings[clipArrayIndex]->pptrCurveMapping.begin());
+ }
+ else
+ {
+ animationSetClips[clipArrayIndex] = NULL;
+ animationClipBindings[clipArrayIndex] = NULL;
+ setClip.m_Bindings.m_IntegerRemap = NULL;
+ }
+
+ clipArrayIndex++;
+ }
+ }
+
+ assert(clipCount == clipArrayIndex);
+
+
+ // Bind to unique curves
+ dynamic_array<GenericBinding> genericBindings (kMemTempAlloc);
+ dynamic_array<GenericBinding> genericPPtrBindings (kMemTempAlloc);
+ dynamic_array<TransformBinding> transformBindings (kMemTempAlloc);
+ dynamic_array<float> constantValues (kMemTempAlloc);
+ int transformBindingsNonConstants;
+
+ CombineUniqueGeneric (animationClipBindings, animationSetClips, clipCount, transformBindings, transformBindingsNonConstants, genericBindings, genericPPtrBindings, constantValues, allocator);
+
+ animationSet->m_DynamicFullValuesConstant = CreateBindingValueArrayConstant (genericBindings.begin(), genericBindings.size(), genericPPtrBindings.begin(), genericPPtrBindings.size(), transformBindings.begin(), transformBindings.size(), allocator);
+
+ for(int layerIter=0; layerIter<animationSet->m_LayerCount; layerIter++)
+ {
+ animationSet->m_DynamicValuesMaskArray[layerIter] = mecanim::CreateValueArrayMask(animationSet->m_DynamicFullValuesConstant,allocator);
+ BindSkeletonMask(*animationSet->m_DynamicFullValuesConstant, *controller->m_LayerArray[layerIter]->m_SkeletonMask, *animationSet->m_DynamicValuesMaskArray[layerIter]);
+ }
+
+ BindAdditionalCurves(*controller->m_Values.Get(), genericBindings, 0, animationSet->m_AdditionalIndexArray);
+
+ BindGravityWeight (animationSet, animationSet->m_DynamicFullValuesConstant);
+
+ // Generate output bindings
+ AnimationSetBindings* outputBindings = allocator.Construct<AnimationSetBindings> ();
+ outputBindings->animationSet = animationSet;
+
+ outputBindings->transformBindingsNonConstantSize = transformBindingsNonConstants;
+ outputBindings->transformBindingsSize = transformBindings.size();
+ outputBindings->transformBindings = allocator.ConstructArray<TransformBinding> (transformBindings.begin(), transformBindings.size());
+
+ outputBindings->genericBindingsSize = genericBindings.size();
+ outputBindings->genericBindings = allocator.ConstructArray<GenericBinding> (genericBindings.begin(), genericBindings.size());
+
+ outputBindings->genericPPtrBindingsSize = genericPPtrBindings.size();
+ outputBindings->genericPPtrBindings = allocator.ConstructArray<GenericBinding> (genericPPtrBindings.begin(), genericPPtrBindings.size());
+
+ outputBindings->constantCurveValueCount = constantValues.size();
+ outputBindings->constantCurveValues = allocator.ConstructArray<float> (constantValues.begin(), constantValues.size());
+
+ return outputBindings;
+}
+
+size_t GetCurveCountForBindingType (UInt32 targetType)
+{
+ if (targetType == kBindTransformRotation)
+ return 4;
+ else if (targetType == kBindTransformPosition || targetType == kBindTransformScale)
+ return 3;
+ else
+ return 1;
+}
+
+static size_t GetCurveCountForBinding (const GenericBinding& binding)
+{
+ if (binding.classID == ClassID(Transform))
+ return GetCurveCountForBindingType(binding.attribute);
+ else
+ return 1;
+}
+
+static mecanim::ValueArrayConstant* CreateBindingValueArrayConstant (const GenericBinding* genericBindings, size_t genericBindingsSize, const GenericBinding* genericPPtrBindings, size_t genericPPtrBindingsSize, const TransformBinding* transformBindings, size_t transformBindingsSize, mecanim::memory::Allocator& alloc)
+{
+ size_t valueCount = genericBindingsSize + transformBindingsSize + genericPPtrBindingsSize;
+ mecanim::ValueArrayConstant* constant = CreateValueArrayConstant (mecanim::kFloatType, valueCount, alloc);
+
+ mecanim::ValueConstant* valueArray = constant->m_ValueArray.Get();
+
+ mecanim::uint32_t valueIndex = 0;
+ mecanim::uint32_t positionCount = 0;
+ mecanim::uint32_t rotationCount = 0;
+ mecanim::uint32_t scaleCount = 0;
+
+ for(int i=0;i<genericBindingsSize;i++,valueIndex++)
+ {
+ valueArray[valueIndex].m_ID = genericBindings[i].attribute;
+ valueArray[valueIndex].m_Index = i;
+ valueArray[valueIndex].m_Type = mecanim::kFloatType;
+ }
+
+ for(int i=0;i<genericPPtrBindingsSize;i++,valueIndex++)
+ {
+ valueArray[valueIndex].m_ID = genericPPtrBindings[i].attribute;
+ valueArray[valueIndex].m_Index = i;
+ valueArray[valueIndex].m_Type = mecanim::kInt32Type;
+ }
+
+ // Currently transform bindings are the only ones with constant curve optimization.
+ // They have to be placed at the end of the ValueArrayConstant so we can reduce it.
+ for(int i=0;i<transformBindingsSize;i++,valueIndex++)
+ {
+ valueArray[valueIndex].m_ID = transformBindings[i].path;
+
+ switch( transformBindings[i].bindType)
+ {
+ case kBindTransformPosition:
+ valueArray[valueIndex].m_Index = positionCount++;
+ valueArray[valueIndex].m_Type = mecanim::kPositionType;
+ break;
+
+ case kBindTransformRotation:
+ valueArray[valueIndex].m_Index = rotationCount++;
+ valueArray[valueIndex].m_Type = mecanim::kQuaternionType;
+ break;
+
+ case kBindTransformScale:
+ valueArray[valueIndex].m_Index = scaleCount++;
+ valueArray[valueIndex].m_Type = mecanim::kScaleType;
+ break;
+
+ default:
+ AssertString("Unsupported");
+ }
+ }
+
+ return constant;
+}
+
+struct BoundIndex
+{
+ enum { kNotInitialized = 0, kIsConstant = 2, kIsNotConstant = 3 };
+
+ mecanim::ValueType type;
+ int index;
+ int curveCount;
+
+ int constantType;
+ float constantValue[4];
+
+ BoundIndex ()
+ {
+ constantType = kNotInitialized;
+ type = mecanim::kLastType;
+ index = -1;
+ curveCount = -1;
+ }
+};
+
+// Currently constant curve optimization is only supported for Transforms
+bool DoesBindingSupportConstantCurveOptimization (const GenericBinding& binding)
+{
+ return binding.classID == ClassID(Transform);
+}
+
+typedef UNITY_MAP(kMemTempAlloc, GenericBinding, BoundIndex) BindingMap;
+
+static void GenerateUniqueBindingMap (AnimationClipBindingConstant** constants, mecanim::animation::AnimationSet::Clip** animationSetClips, size_t clipCount, BindingMap& bindingMap)
+{
+ for (int c=0;c<clipCount;c++)
+ {
+ if (constants[c] == NULL)
+ continue;
+
+ const dynamic_array<GenericBinding>& genericBindingArray = constants[c]->genericBindings;
+
+ const mecanim::animation::Clip& clipData = *animationSetClips[c]->m_Clip->m_Clip;
+ size_t curveIndex = 0;
+ size_t firstConstantIndex = GetClipCurveCount(clipData) - clipData.m_ConstantClip.curveCount;
+ for (int i=0;i<genericBindingArray.size();i++)
+ {
+ const GenericBinding& binding = genericBindingArray[i];
+
+ size_t curveCountForBinding = GetCurveCountForBinding(binding);
+ BoundIndex& boundIndex = bindingMap.insert(std::make_pair (binding, BoundIndex ())).first->second;
+
+ // Detect constant curve values
+ if (curveIndex >= firstConstantIndex && DoesBindingSupportConstantCurveOptimization (binding))
+ {
+ const float* constantCurveValue = &clipData.m_ConstantClip.data[curveIndex - firstConstantIndex];
+ if (boundIndex.constantType == BoundIndex::kNotInitialized)
+ {
+ boundIndex.constantType = BoundIndex::kIsConstant;
+
+ memcpy(boundIndex.constantValue, constantCurveValue, curveCountForBinding * sizeof(float));
+ }
+ else if (boundIndex.constantType == BoundIndex::kIsConstant)
+ {
+ //@TODO: Use approximately??? also in the instance check?
+
+ if (memcmp(boundIndex.constantValue, constantCurveValue, curveCountForBinding * sizeof(float)) != 0)
+ boundIndex.constantType = BoundIndex::kIsNotConstant;
+ }
+ }
+ else
+ {
+ boundIndex.constantType = BoundIndex::kIsNotConstant;
+ }
+
+ curveIndex += curveCountForBinding;
+ }
+ }
+}
+
+static void CombineUniqueGeneric (AnimationClipBindingConstant** constants, mecanim::animation::AnimationSet::Clip** animationSetClips, size_t clipCount, dynamic_array<TransformBinding>& outputCombinedTransformBinding, int& outputNonConstantTransformCount, dynamic_array<GenericBinding>& outputCombinedGenericBinding, dynamic_array<GenericBinding>& outputCombinedGenericPPtrBinding, dynamic_array<float>& constantValues, mecanim::memory::Allocator& alloc)
+{
+ BindingMap bindingMap;
+
+ // Generate unique set of all properties in all clips
+ GenerateUniqueBindingMap (constants, animationSetClips, clipCount, bindingMap);
+
+ // Sorting binding array, normal curves first, constant curves at the end
+ typedef pair<GenericBinding, BoundIndex*> BindingType;
+ dynamic_array<BindingType> bindingsSorted;
+ bindingsSorted.reserve(bindingMap.size());
+
+ for (BindingMap::iterator i=bindingMap.begin();i != bindingMap.end();i++)
+ {
+ if (i->second.constantType == BoundIndex::kIsNotConstant)
+ bindingsSorted.push_back(std::make_pair(i->first, &i->second));
+ }
+ for (BindingMap::iterator i=bindingMap.begin();i != bindingMap.end();i++)
+ {
+ if (i->second.constantType == BoundIndex::kIsConstant)
+ bindingsSorted.push_back(std::make_pair(i->first, &i->second));
+ }
+
+ // Fill in BoundIndex in the bindingMap
+ int positionCount = 0;
+ int rotationCount = 0;
+ int scaleCount = 0;
+ int genericCount = 0;
+ int genericPPtrCount = 0;
+ for (int i=0;i<bindingsSorted.size();i++)
+ {
+ BindingType& binding = bindingsSorted[i];
+
+ if (binding.first.classID == ClassID (Transform))
+ {
+ switch( binding.first.attribute)
+ {
+ case kBindTransformPosition:
+ binding.second->type = mecanim::kPositionType;
+ binding.second->index = positionCount++;
+ binding.second->curveCount = 3;
+ break;
+
+ case kBindTransformRotation:
+ binding.second->type = mecanim::kQuaternionType;
+ binding.second->index = rotationCount++;
+ binding.second->curveCount = 4;
+ break;
+
+ case kBindTransformScale:
+ binding.second->type = mecanim::kScaleType;
+ binding.second->index = scaleCount++;
+ binding.second->curveCount = 3;
+ break;
+
+ default:
+ Assert("Unsupported");
+ }
+ }
+ else if (IsPPtrAnimation(binding.first))
+ {
+ binding.second->type = mecanim::kInt32Type;
+ binding.second->index = genericPPtrCount++;
+ binding.second->curveCount = 1;
+ }
+ else if (!IsMuscleBinding(binding.first))
+ {
+ binding.second->type = mecanim::kFloatType;
+ binding.second->index = genericCount++;
+ binding.second->curveCount = 1;
+ }
+ else
+ {
+ binding.second->type = mecanim::kLastType;
+ binding.second->index = -1;
+ binding.second->curveCount = 1;
+ }
+ }
+
+ // Convert to outputCombinedArray
+ outputCombinedGenericBinding.reserve(genericCount);
+ outputCombinedGenericPPtrBinding.reserve(genericPPtrCount);
+ outputCombinedTransformBinding.reserve(positionCount + rotationCount + scaleCount);
+
+ outputNonConstantTransformCount = 0;
+
+ for (int i=0;i<bindingsSorted.size();i++)
+ {
+ const BindingType& binding = bindingsSorted[i];
+
+ if (binding.first.classID == ClassID (Transform))
+ {
+ TransformBinding& transformBinding = outputCombinedTransformBinding.push_back();
+ transformBinding.path = binding.first.path;
+ transformBinding.bindType = binding.first.attribute;
+
+ if (binding.second->constantType == BoundIndex::kIsNotConstant)
+ outputNonConstantTransformCount = outputCombinedTransformBinding.size();
+ else
+ {
+ for (int k=0;k<binding.second->curveCount;k++)
+ constantValues.push_back(binding.second->constantValue[k]);
+ }
+ }
+ else if (IsPPtrAnimation(binding.first))
+ {
+ Assert(binding.second->constantType == BoundIndex::kIsNotConstant);
+ outputCombinedGenericPPtrBinding.push_back(binding.first);
+ }
+ else if (!IsMuscleBinding(binding.first))
+ {
+ Assert(binding.second->constantType == BoundIndex::kIsNotConstant);
+ outputCombinedGenericBinding.push_back(binding.first);
+ }
+ }
+
+
+ // Generate remap from ValueArray to curveIndex
+ for (int c=0;c<clipCount;c++)
+ {
+ if (animationSetClips[c] == NULL)
+ continue;
+
+ const dynamic_array<GenericBinding>& genericBindingArray = constants[c]->genericBindings;
+
+ mecanim::animation::AnimationSet::Clip* clip = animationSetClips[c];
+ mecanim::animation::ClipBindings& clipBindings = clip->m_Bindings;
+
+ clipBindings.m_PositionIndex = alloc.ConstructArray<mecanim::int16_t>(positionCount);
+ clipBindings.m_QuaternionIndex = alloc.ConstructArray<mecanim::int16_t>(rotationCount);
+ clipBindings.m_ScaleIndex = alloc.ConstructArray<mecanim::int16_t>(scaleCount);
+ clipBindings.m_FloatIndex = alloc.ConstructArray<mecanim::int16_t>(genericCount);
+ clipBindings.m_IntIndex = alloc.ConstructArray<mecanim::int16_t>(genericPPtrCount);
+
+ for (int i=0;i<positionCount;i++)
+ clipBindings.m_PositionIndex[i] = -1;
+ for (int i=0;i<rotationCount;i++)
+ clipBindings.m_QuaternionIndex[i] = -1;
+ for (int i=0;i<scaleCount;i++)
+ clipBindings.m_ScaleIndex[i] = -1;
+ for (int i=0;i<genericCount;i++)
+ clipBindings.m_FloatIndex[i] = -1;
+ for (int i=0;i<genericPPtrCount;i++)
+ clipBindings.m_IntIndex[i] = -1;
+
+ int curveIndex = 0;
+ int totalUsedOptimizedCurveCount = 0;
+ for (int i=0;i<genericBindingArray.size();i++)
+ {
+ const GenericBinding& genericBinding = genericBindingArray[i];
+
+ BindingMap::iterator found = bindingMap.find(genericBinding);
+ Assert(found != bindingMap.end());
+
+ switch (found->second.type)
+ {
+ case mecanim::kPositionType:
+ clipBindings.m_PositionIndex[found->second.index] = curveIndex;
+ break;
+
+ case mecanim::kQuaternionType:
+ clipBindings.m_QuaternionIndex[found->second.index] = curveIndex;
+ break;
+
+ case mecanim::kScaleType:
+ clipBindings.m_ScaleIndex[found->second.index] = curveIndex;
+ break;
+
+ case mecanim::kFloatType:
+ clipBindings.m_FloatIndex[found->second.index] = curveIndex;
+ break;
+
+ case mecanim::kInt32Type:
+ clipBindings.m_IntIndex[found->second.index] = curveIndex;
+ break;
+
+ default:
+ Assert("Unsupported");
+ break;
+ }
+
+ curveIndex += found->second.curveCount;
+
+ if (found->second.constantType == BoundIndex::kIsNotConstant)
+ totalUsedOptimizedCurveCount = curveIndex;
+ }
+
+ // The total used curve count is based on the properties we bind.
+
+ // Muscle clip properties are not bound through the animation set they are pre-bound when building the clip
+ // But we still have to allocate memory for them in the clipoutputs for sampling
+ const mecanim::animation::ClipMuscleConstant& clipMuscleConstant = *clip->m_Clip;
+ for (int m=0;m<mecanim::animation::s_ClipMuscleCurveCount;m++)
+ totalUsedOptimizedCurveCount = std::max<mecanim::int32_t> (clipMuscleConstant.m_IndexArray[m] + 1, totalUsedOptimizedCurveCount);
+
+ clip->m_TotalUsedOptimizedCurveCount = totalUsedOptimizedCurveCount;
+ }
+}
+
+static void BindAdditionalCurves(mecanim::ValueArrayConstant const &valueConstant, dynamic_array<GenericBinding> const& genericBindings, size_t bindingOffset, mecanim::int32_t *additionalIndexArray)
+{
+ for(int genericIter = 0; genericIter < genericBindings.size(); genericIter++)
+ {
+ if (genericBindings[genericIter].classID == ClassID(Animator))
+ {
+ //@TODO: Watch out when switching hash to 64 bit
+ mecanim::int32_t valueIndex = mecanim::FindValueIndex(&valueConstant, mecanim::uint32_t(genericBindings[genericIter].attribute));
+
+ if (valueIndex != -1)
+ {
+ additionalIndexArray[valueIndex] = bindingOffset + genericIter;
+ }
+ }
+ }
+}
+
+static void BindSkeletonMask(const mecanim::ValueArrayConstant& valueConstant, const mecanim::skeleton::SkeletonMask& skeletonMask, mecanim::ValueArrayMask& mask)
+{
+ bool emptyMask = skeletonMask.m_Count == 0;
+
+ for(int maskIter = 0; maskIter < valueConstant.m_Count; maskIter++)
+ {
+ bool maskValue = false;
+
+ if (emptyMask || (valueConstant.m_ValueArray[maskIter].m_Type == mecanim::kFloatType))
+ {
+ maskValue = true;
+ }
+ else
+ {
+ bool found = false;
+
+ for(int skIter = 0; !found && skIter < skeletonMask.m_Count; skIter++)
+ {
+ if(skeletonMask.m_Data[skIter].m_Weight > 0)
+ {
+ found = valueConstant.m_ValueArray[maskIter].m_ID == skeletonMask.m_Data[skIter].m_PathHash;
+ }
+ }
+
+ maskValue = found;
+ }
+
+
+ int index = valueConstant.m_ValueArray[maskIter].m_Index;
+ switch (valueConstant.m_ValueArray[maskIter].m_Type)
+ {
+ case mecanim::kPositionType:
+ mask.m_PositionValues[index] = maskValue;
+ break;
+ case mecanim::kQuaternionType:
+ mask.m_QuaternionValues[index] = maskValue;
+ break;
+ case mecanim::kScaleType:
+ mask.m_ScaleValues[index] = maskValue;
+ break;
+ case mecanim::kFloatType:
+ mask.m_FloatValues[index] = maskValue;
+ break;
+ case mecanim::kInt32Type:
+ mask.m_IntValues[index] = maskValue;
+ break;
+ default:
+ Assert("Unsupported");
+ }
+ }
+}
+}
+}
diff --git a/Runtime/Animation/AnimationSetBinding.h b/Runtime/Animation/AnimationSetBinding.h
new file mode 100644
index 0000000..971df71
--- /dev/null
+++ b/Runtime/Animation/AnimationSetBinding.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "AnimationClipBindings.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+
+class RuntimeAnimatorController;
+
+class AnimationClip;
+
+namespace mecanim { namespace animation { struct AnimationSet; struct ControllerConstant; } }
+namespace mecanim { namespace memory { class Allocator; } }
+typedef std::vector<PPtr<AnimationClip> > AnimationClipVector;
+
+namespace UnityEngine
+{
+namespace Animation
+{
+
+
+struct TransformBinding
+{
+ BindingHash path;
+ int bindType;
+};
+
+struct AnimationSetBindings
+{
+ size_t genericBindingsSize;
+ GenericBinding* genericBindings;
+
+ size_t genericPPtrBindingsSize;
+ GenericBinding* genericPPtrBindings;
+
+ size_t transformBindingsNonConstantSize;
+ size_t transformBindingsSize;
+ TransformBinding* transformBindings;
+
+
+ // See ConstantCurveOptimization below:
+ size_t constantCurveValueCount;
+ float* constantCurveValues;
+
+ mecanim::animation::AnimationSet* animationSet;
+};
+
+AnimationSetBindings* CreateAnimationSetBindings (mecanim::animation::ControllerConstant const* controller, AnimationClipVector const& clips, mecanim::memory::Allocator& allocator);
+void DestroyAnimationSetBindings (AnimationSetBindings* bindings, mecanim::memory::Allocator& allocator);
+size_t GetCurveCountForBindingType (UInt32 targetType);
+
+
+/*
+ *** Constant Curve optimization overview ***
+
+We found that animationclips for generic characters in most cases contain a lot of scale curves and often also translation curves which are completely constant.
+This has a massive negative impact on performance. StreamedClip sampling is negatively affected because all the coefficients need to be evaluated.
+But more importantly the ValueArray used for blending becomes very big. Especially since blending scale curves is pretty expensive (log scale blend)
+
+It would have been easy to simply remove the curves that are constant when importing the clips,
+but there is no good automatic default for it because in some cases the user actually makes constant curves intentionally to get an effect when blending between clips.
+
+So when creating the mecanim clip we classify all curves as constant curves and streamedclip curves. Constant Curves are put at the end.
+
+Constantclip evaluation is trivial, simply a memcpy.
+
+When binding we also detect if all the clips have the same constant values. If they do we put the constant bindings at the end of the ValueArrayConstant.
+On the instance we then need to verify if the constant curves on the clip matches the default values on the instance.
+This is because defaultValues are used for blending. If they are used for blending and different between defaults and clip values, then they must be represented in the ValueArray.
+
+Thus on the instance we have a subset of the value array and on the instance we can simply reduce the value array by cutting of the end of the ValueArray.
+All data is laid out to be able to do this in the AnimationSetBinding.
+
+*/
+}
+}
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);
+}
+
diff --git a/Runtime/Animation/AnimationState.h b/Runtime/Animation/AnimationState.h
new file mode 100644
index 0000000..c9c92ee
--- /dev/null
+++ b/Runtime/Animation/AnimationState.h
@@ -0,0 +1,269 @@
+#pragma once
+#include <vector>
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/BaseClasses/RefCounted.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+template<class T> class AnimationCurveTpl; typedef AnimationCurveTpl<float> AnimationCurve;
+template<class T> class AnimationCurveTpl; typedef AnimationCurveTpl<float> AnimationCurveBase;
+class AnimationClip;
+class Transform;
+class AABB;
+namespace Unity { class Component; }
+
+#define GET_SET_REF(a,b,c) void Set##b (a& val) { c = val; } a& Get##b () const {return c; }
+
+#define kReallySmallWeight 0.0001F
+#define kReallySmallFadeTime 0.001F
+
+enum {
+ kRebindDirtyMask = 1 << 0,
+ kLayersDirtyMask = 1 << 1
+};
+
+
+class AnimationState : public TrackedReferenceBase
+{
+private:
+ // Indicates state of m_AnimationEventIndex
+ enum {
+ // m_AnimationEventIndex is valid
+ kAnimationEventState_HasEvent = 0,
+ // m_AnimationEventIndex need to be researched
+ kAnimationEventState_Search,
+ // m_AnimationEventIndex there are no more keys available, no need to search
+ kAnimationEventState_NotFound,
+ // This is very special case:
+ // m_AnimationEventIndex points to a valid index and AnimationState is paused,
+ // but this event has been triggered already, so when AnimationState state is unpaused
+ // we have to continue triggering event on left or right side of this event
+ // (depending on the sign of AnimationState speed)
+ kAnimationEventState_PausedOnEvent,
+
+ // Used for assert
+ kAnimationEventState__Count
+ };
+
+public:
+
+ enum { kBlend = 0, kAdditive };
+
+ AnimationState ();
+ ~AnimationState ();
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ /// Makes the animation state reach a /target/ blend weight in length seconds.
+ /// If the animation is already fading towards target and it would reach the target
+ /// faster, then the weight speed will not be modified.
+ /// if stopWhenFaded is enabled, the animation will stop when the target is reached.
+ void SetWeightTarget (float target, float length, bool stopWhenFaded);
+
+ void SetWeightTargetImmediate (float target, bool stopWhenFaded);
+
+ void Stop ();
+
+ // This automatically sets m_LastGlobalTime from globa
+ void SetEnabled (bool enabled);
+
+ bool GetEnabled () const { return m_Enabled; }
+
+ void SetTime (float time);
+ const float GetTime() const { return m_Time; }
+
+ int GetWrapMode () const { return m_WrapMode; }
+ void SetWrapMode (int mode);
+
+ GET_SET_REF(const UnityStr, Name, m_Name)
+ GET_SET_REF(const UnityStr, ParentName, m_ParentName)
+
+ void SetLayer (int layer) { m_Layer = layer; m_DirtyMask |= kLayersDirtyMask; }
+ int GetLayer () const { return m_Layer; }
+
+ void SetWeight (float val) { m_Weight = val; }
+ float GetWeight () const { return m_Weight; }
+
+ void SetNormalizedSpeed (float speed) { m_SyncedSpeed = m_Speed = speed * GetLength(); }
+ float GetNormalizedSpeed () const { return m_Speed / GetLength(); }
+
+ void SetSpeed (float speed);
+ float GetSpeed () const { return m_Speed; }
+ float GetSyncedSpeed () const { return m_SyncedSpeed; }
+
+ void SetNormalizedTime (float time) { SetTime( time * GetLength()); }
+ float GetNormalizedTime () const { return m_Time / GetLength(); }
+
+ float GetLength () const { return m_CachedRange.second; }
+
+ /// Sets the fadeout length and stop time based on the used wrapmode.
+ void SetupFadeout (float length);
+
+ int GetBlendMode() const { return m_BlendMode; }
+ void SetBlendMode(int mode) { m_BlendMode = mode; }
+
+ AnimationClip* GetClip() { return m_Clip; }
+
+ void ClearDirtyMask() { m_DirtyMask = 0; }
+ UInt32 GetDirtyMask () const { return m_DirtyMask; }
+
+ // Returns true if the state is enabled and has a reasonably high weight
+ inline bool ShouldUse() const;
+
+ // Used for layer syncing
+ void SetNormalizedSyncedSpeed (float speed) { m_SyncedSpeed = speed * GetLength(); }
+
+ bool UpdateAnimationState (double globalTime, Unity::Component& animation);
+
+ void Init(const UnityStr& name, AnimationClip* clip, double globalTime, int wrap, bool isClone = false);
+
+ void CleanupCurves ();
+ void SetAutoCleanup(){ m_AutoCleanup = 1; }
+ void ForceAutoCleanup() { SetAutoCleanup(); m_ShouldCleanup = true; }
+ bool ShouldAutoCleanupNow () { return m_ShouldCleanup; }
+ bool IsClone() { return m_IsClone; }
+
+ typedef AnimationCurveBase** Curves;
+ Curves GetCurves() { return m_Curves; }
+ Curves const GetCurves() const { return m_Curves; }
+
+ void AllocateCurves(int count);
+ void SetClonedCurves(AnimationState& state);
+
+ bool ShouldMixTransform (Transform& transform);
+ void AddMixingTransform(Transform& transform, bool recursive);
+ void RemoveMixingTransform(Transform& transform);
+
+ /// When an animation is stopped and it is the only animation playing.
+ /// Then you usually want the last frame to display before the animation stops.
+ /// For example an elevator moving up. It should always make sure the last frame gets sampled.
+ /// -> Now unfortunately we stop time during UpdateAnimationState which means the time gets reset
+ /// -> Which wrapped time and weight will be set to zero when actually sampling the animation.
+ /// -> So we just fix the very specific, single animation playing and stopping case,
+ /// by storing the wrap mode prior to animation stop and then afterwards reverting it again.
+ /// *** I guess the right way to solve this would be to make animation stopping happen after sampling or something...
+ void SetupUnstoppedState ();
+ void CleanupUnstoppedState ();
+
+ bool FireEvents (const float deltaTime, float newWrappedTime, bool reverse, Unity::Component& animation, const float beginTime, const float offsetTime, const bool reverseOffsetTime);
+
+ // We made a bunch of fixes in Unity 3.2, but we couldn't use them since it breaks backwards compatibility,
+ // so we enable the fixes only for 3.2 content
+ // TODO : get rid of this function and all related backwards compatible functions as soon as we are allowed to break backwards compatibility
+ static bool UseUnity32AnimationFixes();
+
+ // Same story (see above) with animation fixes in Unity 3.4
+ static bool UseUnity34AnimationFixes();
+
+ // Same story (see above) with animation fixes in Unity 3.5
+ static bool UseUnity35AnimationFixes();
+
+private:
+ bool UseStopTime() const { return m_WrapMode == kClamp || m_WrapMode == kDefaultWrapMode; }
+ void SetupStopTime();
+
+ typedef List< ListNode<AnimationState> > AnimationStateList;
+ static void DidModifyAnimationClip (AnimationClip* clip, AnimationStateList& states);
+
+ bool UpdateFading_Before32(float deltaTime);
+ bool UpdateFading(float deltaTime);
+
+ bool UpdateBlendingWeight(const float deltaTime, const bool instantBlend);
+
+private:
+ Curves m_Curves;
+// int m_CurvesCount;
+
+ float m_Weight;
+ float m_WrappedTime; // Always keep in animation clip length range
+
+ double m_Time; // Keeps on increasing forever -> higher precision
+ double m_LastGlobalTime; // Keeps on increasing forever -> higher precision
+
+ int m_Layer;
+ float m_Speed;
+ float m_SyncedSpeed;
+
+ float m_StopTime;
+
+ float m_FadeOutLength;
+ float m_WeightTarget;
+
+ UInt32 m_FadeBlend : 1;
+ UInt32 m_Enabled : 1;
+ UInt32 m_StopWhenFadedOut : 1;
+ UInt32 m_AutoCleanup : 1;
+ UInt32 m_OwnsCurves : 1;
+ UInt32 m_IsFadingOut : 1;
+ UInt32 m_ShouldCleanup : 1;
+ UInt32 m_HasAnimationEvent : 1;
+ UInt32 m_IsClone : 1;
+ // make sure that there are enough bits to store kAnimationEventStates
+ UInt32 m_AnimationEventState : 2;
+ int m_AnimationEventIndex;
+
+ UInt32 m_DirtyMask;
+
+ int m_WrapMode; ///< enum { Default = 0, Once = 1, Loop = 2, PingPong = 4, ClampForever = 8 }
+ int m_BlendMode; ///< enum { Blend = 0, Additive = 1 }
+
+ float m_WeightDelta;
+
+ ///@TODO: FIXME HACKED Time stopping
+ float m_UnstoppedLastWrappedTime;
+ float m_UnstoppedLastWeight;
+
+ std::pair<float, float> m_CachedRange;
+
+ AnimationClip* m_Clip;
+ ListNode<AnimationState> m_AnimationClipNode;
+
+ UnityStr m_Name;
+ UnityStr m_ParentName;
+
+ typedef std::map<PPtr<Transform>, bool> MixingTransforms;
+ MixingTransforms m_MixingTransforms;
+
+ friend class Animation;
+};
+
+float WrapTime (float time, const std::pair<float, float>& range, int m_WrapMode);
+
+inline bool AnimationState::ShouldUse() const
+{
+ return m_Clip && m_Enabled && m_Weight > kReallySmallWeight;
+}
+
+// For debugging purposes display some of the animation state information!
+#if UNITY_EDITOR
+#include "Runtime/Serialize/SerializeTraits.h"
+template<>
+class SerializeTraits<AnimationState*> : public SerializeTraitsBase<AnimationState*>
+{
+ public:
+
+ typedef AnimationState* value_type;
+ inline static const char* GetTypeString (void*) { return "AnimationState"; }
+ inline static bool IsAnimationChannel () { return false; }
+ inline static bool MightContainPPtr () { return true; }
+ inline static bool AllowTransferOptimization () { return false; }
+
+ template<class TransferFunction> inline
+ static void Transfer (value_type& data, TransferFunction& transfer)
+ {
+ TRANSFER_PROPERTY_DEBUG(UnityStr, m_Name, data->GetName)
+ TRANSFER_PROPERTY_DEBUG(bool, m_Enabled, data->GetEnabled)
+ transfer.Align();
+ TRANSFER_PROPERTY_DEBUG(float, m_Weight, data->GetWeight)
+ TRANSFER_PROPERTY_DEBUG(float, m_Time, data->GetTime)
+ TRANSFER_PROPERTY_DEBUG(float, m_Speed, data->GetSpeed)
+ TRANSFER_PROPERTY_DEBUG(float, m_SyncedSpeed, data->GetSyncedSpeed)
+ TRANSFER_PROPERTY_DEBUG(int, m_WrapMode, data->GetWrapMode)
+ TRANSFER_PROPERTY_DEBUG(int, m_BlendMode, data->GetBlendMode)
+ TRANSFER_PROPERTY_DEBUG(PPtr<AnimationClip>, m_Clip, data->GetClip)
+ }
+};
+
+#endif
diff --git a/Runtime/Animation/AnimationStateNetworkProvider.cpp b/Runtime/Animation/AnimationStateNetworkProvider.cpp
new file mode 100644
index 0000000..3393736
--- /dev/null
+++ b/Runtime/Animation/AnimationStateNetworkProvider.cpp
@@ -0,0 +1,51 @@
+#include "UnityPrefix.h"
+#include "AnimationStateNetworkProvider.h"
+#include "Runtime/Interfaces/IAnimationStateNetworkProvider.h"
+#include "AnimationState.h"
+#include "Animation.h"
+
+class AnimationStateNetworkProvider : public IAnimationStateNetworkProvider
+{
+public:
+
+ virtual int GetNetworkAnimationStateCount (Animation& animation)
+ {
+ return animation.GetAnimationStateCount();
+ }
+
+ virtual void GetNetworkAnimationState (Animation& animation, AnimationStateForNetwork* output, int count)
+ {
+ for (int i=0;i<count;i++)
+ {
+ AnimationState& state = animation.GetAnimationStateAtIndex(i);
+
+ output[i].enabled = state.GetEnabled ();
+ output[i].weight = state.GetWeight ();
+ output[i].time = state.GetTime ();
+ }
+ }
+
+ virtual void SetNetworkAnimationState (Animation& animation, const AnimationStateForNetwork* serialize, int count)
+ {
+ for (int i=0;i<count;i++)
+ {
+ AnimationState& state = animation.GetAnimationStateAtIndex(i);
+
+ state.SetEnabled (serialize[i].enabled);
+ state.SetWeight (serialize[i].weight);
+ state.SetTime (serialize[i].time);
+ }
+ }
+};
+
+void InitializeAnimationStateNetworkProvider ()
+{
+ SetIAnimationStateNetworkProvider(UNITY_NEW_AS_ROOT(AnimationStateNetworkProvider, kMemPhysics, "AnimationStateNetworkInterface", ""));
+}
+
+void CleanupAnimationStateNetworkProvider ()
+{
+ AnimationStateNetworkProvider* module = reinterpret_cast<AnimationStateNetworkProvider*> (GetIAnimationStateNetworkProvider ());
+ UNITY_DELETE(module, kMemPhysics);
+ SetIAnimationStateNetworkProvider(NULL);
+} \ No newline at end of file
diff --git a/Runtime/Animation/AnimationStateNetworkProvider.h b/Runtime/Animation/AnimationStateNetworkProvider.h
new file mode 100644
index 0000000..e7abd05
--- /dev/null
+++ b/Runtime/Animation/AnimationStateNetworkProvider.h
@@ -0,0 +1,6 @@
+#pragma once
+
+class Animation;
+
+void InitializeAnimationStateNetworkProvider ();
+void CleanupAnimationStateNetworkProvider(); \ No newline at end of file
diff --git a/Runtime/Animation/AnimationUtility.cpp b/Runtime/Animation/AnimationUtility.cpp
new file mode 100644
index 0000000..4ae7dc0
--- /dev/null
+++ b/Runtime/Animation/AnimationUtility.cpp
@@ -0,0 +1,271 @@
+#include "UnityPrefix.h"
+
+#include "AnimationUtility.h"
+#include "AnimationClip.h"
+#include "BoundCurveDeprecated.h"
+#include "Animator.h"
+#include "AnimationState.h"
+#include "AnimationBinder.h"
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Graphics/Transform.h"
+
+#include "Runtime/BaseClasses/IsPlaying.h"
+
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+
+#include "Runtime/Misc/GameObjectUtility.h"
+
+PROFILER_INFORMATION (gSampleAnimationClip, "GameObject.SampleAnimation", kProfilerAnimation);
+
+void SampleEulerHint (Transform& transform, AnimationClip& clip, float time)
+{
+ // Sample euler hint curves, only in edit mode
+ // This is necessary for euler angles to look sensible when sampling animations
+#if UNITY_EDITOR
+ if (!IsWorldPlaying())
+ {
+ AnimationClip::FloatCurves& eulerCurves = clip.GetEulerEditorCurves();
+ for (AnimationClip::FloatCurves::iterator i=eulerCurves.begin();i != eulerCurves.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ // Lookup without path
+ Transform* child = &transform;
+ if (!i->path.empty())
+ {
+ child = FindRelativeTransformWithPath(*child, i->path.c_str());
+ if (child == NULL)
+ continue;
+ }
+
+ if (!i->attribute.empty())
+ {
+ int c = i->attribute[i->attribute.size()-1] - 'x';
+ c = clamp(c, 0, 2);
+ float value = i->curve.EvaluateClamp(time);
+ child->m_LocalEulerAnglesHint[c] = value;
+ }
+ }
+ }
+#endif
+}
+
+
+void SampleAnimation (Unity::GameObject& go, AnimationClip& clip, float inTime, int wrapMode)
+{
+ PROFILER_AUTO(gSampleAnimationClip, &go)
+
+ AnimationClip::QuaternionCurves& rot = clip.GetRotationCurves();
+ AnimationClip::Vector3Curves& pos = clip.GetPositionCurves();
+ AnimationClip::Vector3Curves& scale = clip.GetScaleCurves();
+ AnimationClip::FloatCurves& floats = clip.GetFloatCurves();
+
+ CurveID curveID;
+ BoundCurveDeprecated bind;
+
+ float time = WrapTime(inTime, clip.GetRange(), wrapMode);
+
+ AnimationBinder& binder = GetAnimationBinder();
+ Transform& transform = go.GetComponent(Transform);
+
+ Animator* animator = go.QueryComponent(Animator);
+ if (animator)
+ {
+ if (animator->Sample (clip, inTime))
+ {
+ SampleEulerHint (transform, clip, time);
+ return;
+ }
+ }
+
+ // Sample rotations
+ for (AnimationClip::QuaternionCurves::iterator i=rot.begin();i != rot.end();i++)
+ {
+ AnimationCurveQuat& curve = i->curve;
+ if (!curve.IsValid ())
+ continue;
+
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalRotation", 0);
+ if (binder.BindCurve(curveID, bind, transform))
+ {
+ Quaternionf result = NormalizeSafe(curve.EvaluateClamp(time));
+ DebugAssertIf(!IsFinite(result));
+
+ *reinterpret_cast<Quaternionf*>(bind.targetPtr) = result;
+ bind.targetObject->AwakeFromLoad(kDefaultAwakeFromLoad);
+ bind.targetObject->SetDirty();
+ }
+ }
+
+ // Sample positions
+ for (AnimationClip::Vector3Curves::iterator i=pos.begin();i != pos.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalPosition", 0);
+ if (binder.BindCurve(curveID, bind, transform))
+ {
+ Vector3f result = i->curve.EvaluateClamp(time);
+ DebugAssertIf(!IsFinite(result));
+
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ bind.targetObject->AwakeFromLoad(kDefaultAwakeFromLoad);
+ bind.targetObject->SetDirty();
+ }
+ }
+
+
+ // Sample scale
+ for (AnimationClip::Vector3Curves::iterator i=scale.begin();i != scale.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ curveID = CurveID (i->path.c_str(), ClassID(Transform), NULL, "m_LocalScale", 0);
+ if (binder.BindCurve(curveID, bind, transform))
+ {
+ Vector3f result = i->curve.EvaluateClamp(time);
+ DebugAssertIf(!IsFinite(result));
+
+ *reinterpret_cast<Vector3f*>(bind.targetPtr) = result;
+ bind.targetObject->AwakeFromLoad(kDefaultAwakeFromLoad);
+ bind.targetObject->SetDirty();
+ }
+ }
+
+ // Sample arbitrary floats
+ for (AnimationClip::FloatCurves::iterator i=floats.begin();i != floats.end();i++)
+ {
+ if (!i->curve.IsValid ())
+ continue;
+
+ curveID = CurveID (i->path.c_str(), i->classID, i->script, i->attribute.c_str(), 0);
+ if (binder.BindCurve(curveID, bind, transform))
+ {
+ float result = i->curve.EvaluateClamp(time);
+ DebugAssertIf(!IsFinite(result));
+
+ AnimationBinder::SetFloatValue(bind, result);
+ AnimationBinder::SetValueAwakeGeneric(bind);
+ }
+ }
+
+ SampleEulerHint (transform, clip, time);
+}
+
+
+mecanim::crc32 AppendPathToHash(const mecanim::crc32& nameHash, const char* path)
+{
+ mecanim::crc32 childNameHash = nameHash;
+ if (childNameHash.checksum() != 0)
+ childNameHash.process_bytes("/", 1);
+ childNameHash.process_bytes(path, strlen(path));
+ return childNameHash;
+}
+
+Transform* FindChildWithID( Transform* transform, const mecanim::crc32& nameHash, mecanim::uint32_t id, mecanim::crc32& outFoundNameHash, int childStartHint)
+{
+ int childCount = transform->GetChildrenCount();
+ for (int i = 0 ; i < childCount ; ++i)
+ {
+ mecanim::crc32 currentHash = AppendPathToHash(nameHash, transform->GetChild((i+childStartHint)%childCount).GetName());
+ if(id == currentHash.checksum())
+ {
+ outFoundNameHash = currentHash;
+ return &transform->GetChild((i+childStartHint)%childCount);
+ }
+ }
+
+ return 0;
+}
+
+
+
+int HiearchyMatches(Transform* transform, const mecanim::skeleton::Skeleton* skeleton, int skeletonIndex, const mecanim::crc32& nameHash)
+{
+ int currentMatchCount = 0 ;
+ int childStartHint = 0 ;
+
+ for(int i = skeletonIndex ; i < skeleton->m_Count ; i++)
+ {
+ if(skeleton->m_Node[i].m_ParentId == skeletonIndex)
+ {
+ mecanim::crc32 childHash;
+ Transform* childTransform = FindChildWithID(transform, nameHash, skeleton->m_ID[i], childHash, childStartHint);
+
+ if(childTransform)
+ {
+ currentMatchCount++;
+ currentMatchCount += HiearchyMatches(childTransform, skeleton, i, childHash);
+ childStartHint++;
+ }
+
+ }
+ }
+
+ return currentMatchCount;
+}
+
+int HiearchyMatchesOpt(Transform* transform, const mecanim::skeleton::Skeleton* skeleton, const mecanim::uint32_t* nameArray)
+{
+ int matchCount = 0;
+
+ for (Transform::iterator transformIt = transform->begin(); transformIt != transform->end(); ++transformIt)
+ {
+ for(mecanim::int32_t i = 0 ; i < skeleton->m_Count ; ++i)
+ {
+ if(nameArray[i] == mecanim::processCRC32((*transformIt)->GetName()))
+ {
+ matchCount++;
+ continue;
+ }
+ }
+ }
+
+ return matchCount;
+}
+
+
+void BuildTransformList(Transform& root, dynamic_array<Transform*>& outTransforms)
+{
+ outTransforms.push_back(&root);
+ int childCount = root.GetChildrenCount();
+ for (int i = 0 ; i < childCount ; ++i)
+ BuildTransformList(root.GetChild(i), outTransforms);
+}
+
+Transform* FindAvatarRoot(const mecanim::skeleton::Skeleton* skeleton, const mecanim::uint32_t* nameArray, Transform& root, bool hasTransformHierarchy)
+{
+ int bestMatchCount = 0;
+ Transform* animationRoot = 0;
+
+ dynamic_array<Transform*> allTransforms (kMemTempAlloc);
+ allTransforms.reserve(skeleton->m_Count*2);
+ BuildTransformList(root, allTransforms);
+
+ for(int i = 0 ; i < allTransforms.size() ; ++i)
+ {
+ int currentMatchCount = 0 ;
+
+ if(hasTransformHierarchy)
+ currentMatchCount = HiearchyMatches(allTransforms[i], skeleton, 0, mecanim::crc32()); // find the transform that matches best the Avatar Hierarchy
+ else
+ currentMatchCount = HiearchyMatchesOpt(allTransforms[i], skeleton, nameArray); // find the Transform that has the most direct childs in the Avatar Skeleton
+
+ if(currentMatchCount > bestMatchCount)
+ {
+ bestMatchCount = currentMatchCount;
+ animationRoot = allTransforms[i];
+ }
+
+ if( bestMatchCount >= (allTransforms.size() - 1 - i) ) // early out, since we cant have a better score than the remaning transform count.
+ return animationRoot;
+ }
+
+ return animationRoot;
+}
diff --git a/Runtime/Animation/AnimationUtility.h b/Runtime/Animation/AnimationUtility.h
new file mode 100644
index 0000000..7a92ef8
--- /dev/null
+++ b/Runtime/Animation/AnimationUtility.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Runtime/mecanim/types.h"
+
+class AnimationClip;
+class Transform;
+namespace Unity { class GameObject; }
+namespace mecanim { class crc32 ; namespace skeleton { struct Skeleton; } }
+
+
+void SampleAnimation (Unity::GameObject& go, AnimationClip& clip, float inTime, int wrapMode);
+
+
+mecanim::crc32 AppendPathToHash(const mecanim::crc32& nameHash, const char* path);
+Transform* FindChildWithID( Transform* transform, const mecanim::crc32& nameHash, mecanim::uint32_t id);
+
+Transform* FindAvatarRoot(const mecanim::skeleton::Skeleton* skeleton, const mecanim::uint32_t* nameArray, Transform& root, bool hasTransformHierarchy);
diff --git a/Runtime/Animation/Animator.cpp b/Runtime/Animation/Animator.cpp
new file mode 100644
index 0000000..a8db798
--- /dev/null
+++ b/Runtime/Animation/Animator.cpp
@@ -0,0 +1,2674 @@
+#include "UnityPrefix.h"
+
+#include "Animator.h"
+
+#include "Runtime/Animation/OptimizeTransformHierarchy.h"
+
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/mecanim/animation/damp.h"
+#include "Runtime/mecanim/generic/stringtable.h"
+
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "AnimatorManager.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "AnimationClip.h"
+#include "AnimationSetBinding.h"
+#include "MecanimAnimation.h"
+
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/GameCode/RootMotionData.h"
+#include "Runtime/BaseClasses/SupportedMessageOptimization.h"
+#include "Runtime/BaseClasses/EventIDs.h"
+
+#include "Runtime/Graphics/Transform.h"
+
+#include "Runtime/Math/Vector4.h"
+
+#include "AnimatorController.h"
+#include "GenericAnimationBindingCache.h"
+#include "Avatar.h"
+#include "AnimatorOverrideController.h"
+
+#include "Runtime/mecanim/generic/typetraits.h"
+
+#include "Runtime/Serialize/SerializeTraits.h"
+#include "AnimatorGenericBindings.h"
+
+#include "Runtime/Animation/AnimationClip.h"
+#include "Runtime/Animation/AnimationUtility.h"
+
+#define ENABLE_DETAILED_SINGLE_THREAD_PROFILER 0
+
+#define ENABLE_MULTITHREADED_ANIMATION ENABLE_MULTITHREADED_CODE && (!ENABLE_DETAILED_SINGLE_THREAD_PROFILER)
+using namespace UnityEngine::Animation;
+Animator::Animator(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode),
+m_BehaviourIndex(-1),
+m_FixedBehaviourIndex(-1),
+m_DeltaPosition(Vector3f::zero),
+m_DeltaRotation(Quaternionf::identity()),
+m_PivotPosition(0,0,0),
+m_MatchPosition(Vector3f::zero),
+m_MatchRotation(Quaternionf::identity()),
+m_MatchStartTime(-1),
+m_MatchStateID(-1),
+m_MustCompleteMatch(false),
+m_MatchTargetMask(Vector3f::one, 0),
+m_ApplyRootMotion(true),
+m_AnimatePhysics(false),
+m_Visible(false),
+m_CullingMode (kCullAlwaysAnimate),
+m_Speed(1),
+m_FireEvents(true),
+m_LogWarnings(true),
+m_AvatarPlayback(label),
+m_RecorderMode(eNormal),
+m_PlaybackDeltaTime(0),
+m_PlaybackTime(0),
+m_HasTransformHierarchy(true),
+m_AnimatorAvatarNode(this),
+m_AnimatorControllerNode(this),
+m_SamplingDataSet(256*1024),
+mAlloc(kMemAnimation)
+{
+
+}
+
+Animator::~Animator()
+{
+ Assert(m_EvaluationDataSet.m_AvatarMemory == NULL && m_EvaluationDataSet.m_ControllerConstant == NULL && m_EvaluationDataSet.m_GenericBindingConstant == NULL && m_ContainedRenderers.size() == 0);
+}
+
+void Animator::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+ CreateObject();
+ InitializeVisibilityCulling();
+
+ UpdateInManager();
+}
+
+void Animator::CheckConsistency()
+{
+}
+
+void Animator::Reset ()
+{
+ Super::Reset();
+
+ m_CullingMode = kCullAlwaysAnimate;
+ m_ApplyRootMotion = true;
+ m_AnimatePhysics = false;
+ m_HasTransformHierarchy = true;
+}
+
+IMPLEMENT_OBJECT_SERIALIZE (Animator)
+IMPLEMENT_CLASS_HAS_INIT (Animator)
+
+template<class TransferFunction>
+void Animator::Transfer (TransferFunction& transfer)
+{
+ transfer.SetVersion(2);
+
+ Super::Transfer (transfer);
+
+ transfer.Transfer (m_Avatar, "m_Avatar");
+ transfer.Transfer (m_Controller, "m_Controller");
+
+ TRANSFER_ENUM (m_CullingMode);
+ transfer.Transfer (m_ApplyRootMotion, "m_ApplyRootMotion", kDontAnimate);
+ transfer.Transfer (m_AnimatePhysics, "m_AnimatePhysics", kDontAnimate);
+ transfer.Transfer (m_HasTransformHierarchy, "m_HasTransformHierarchy", kDontAnimate);
+}
+
+void Animator::AddToManager ()
+{
+ GetAnimatorManager().AddAnimator(*this);
+}
+
+void Animator::RemoveFromManager ()
+{
+ GetAnimatorManager().RemoveAnimator(*this);
+}
+
+void Animator::Deactivate (DeactivateOperation operation)
+{
+ Super::Deactivate(operation);
+ ClearObject();
+ ClearContainedRenderers();
+}
+
+
+void Animator::TransformChanged (int change)
+{
+ // No need to initialize it
+ if (!IsInitialize())
+ return;
+
+ // Teleport
+ Transform& avatarTransform = GetComponent(Transform);
+ if (change & Transform::kPositionChanged)
+ SetAvatarPosition(avatarTransform.GetPosition());
+
+ if (change & Transform::kRotationChanged)
+ SetAvatarRotation(avatarTransform.GetRotation());
+
+ if(change & Transform::kScaleChanged)
+ SetAvatarScale(avatarTransform.GetWorldScaleLossy());
+}
+
+void Animator::OnAddComponent(Component* com)
+{
+ Renderer* renderer = dynamic_pptr_cast<Renderer*>(com);
+ if(renderer)
+ InitializeVisibilityCulling ();
+}
+
+void Animator::InitializeClass ()
+{
+ mecanim::memory::Profiler::StaticInitialize();
+
+ REGISTER_MESSAGE (Animator, kTransformChanged, TransformChanged, int);
+ REGISTER_MESSAGE_VOID(Animator, kDidModifyAnimatorController, ClearObject);
+ REGISTER_MESSAGE_VOID(Animator, kDidModifyMotion, ClearObject); // an animationClip sends this when deleted
+ REGISTER_MESSAGE_VOID(Animator, kDidModifyAvatar, ClearObject);
+
+ ///@TODO: This doesn't really cover any real world cases...
+ /// adding new chidlren is not covered, adding component to childeren is not covered.
+ REGISTER_MESSAGE_PTR (Animator, kDidAddComponent, OnAddComponent, Component);
+
+ AnimatorManager::InitializeClass();
+ MecanimAnimation::InitializeClass();
+ mecanim::animation::ControllerConstant::InitializeClass();
+ mecanim::animation::AvatarConstant::InitializeClass();
+ mecanim::statemachine::StateConstant::InitializeClass();
+
+ mecanim::crc32::crc32_table_type::init_table();
+ mecanim::animation::InitializeMuscleClipTables ();
+
+ Assert(mecanim::animation::FindMuscleIndex(0) == -1);
+ Assert(mecanim::animation::FindMuscleIndex(mecanim::processCRC32 ("MotionT.x")) == 0);
+}
+
+void Animator::CleanupClass ()
+{
+ AnimatorManager::CleanupClass();
+ MecanimAnimation::CleanupClass();
+ mecanim::memory::Profiler::StaticDestroy();
+}
+
+PROFILER_INFORMATION (gAnimatorUpdate, "Animator.Update", kProfilerAnimation);
+PROFILER_INFORMATION (gAnimatorInitialize, "Animator.Initialize", kProfilerAnimation);
+
+PROFILER_INFORMATION (gProfileApplyRootMotion, "Apply Root Motion", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileFKStep, "FK & Statemachine", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileRetarget, "Retarget", kProfilerAnimation);
+PROFILER_INFORMATION (gRetargetAndPrepareIK, "Retarget", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileAvatarIK, "IK & Final Pose Computation", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileAvatarWrite, "Write", kProfilerAnimation);
+
+PROFILER_INFORMATION (gAnimatorSendTransformChanged, "Animator.SendTransformChanged", kProfilerAnimation);
+PROFILER_INFORMATION (gAnimatorSetGenericProperties, "Animator.ApplyGenericAnimatedProperties", kProfilerAnimation);
+PROFILER_INFORMATION (gAnimatorSetTransformDirty, "Animator.EditorOnlySetDirty", kProfilerAnimation);
+PROFILER_INFORMATION (gAnimatorWriteSkeletonPose, "Animator.WriteSkeletonPose", kProfilerAnimation);
+
+PROFILER_INFORMATION (gProfileDetailSM, "EvaluateAvatarSM", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileDetailFK, "EvaluateAvatarFK", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileDetailAvatarIK, "EvaluateAvatarIK", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileDetailAvatarEnd, "EvaluateAvatarEnd", kProfilerAnimation);
+PROFILER_INFORMATION (gProfileSample, "Animator.Sample", kProfilerAnimation);
+
+PROFILER_INFORMATION (gProfileSetupDataSet, "Animator.SetupDataSet", kProfilerAnimation);
+
+#if ENABLE_DETAILED_SINGLE_THREAD_PROFILER
+#define PROFILER_AUTO_DETAIL(x,o) PROFILER_AUTO(x, o)
+#else
+#define PROFILER_AUTO_DETAIL(x,o)
+#endif
+
+
+static bool DoesLayerHaveIKPass(int layerIndex, const mecanim::animation::ControllerConstant& controller)
+{
+ return layerIndex < controller.m_LayerCount && controller.m_LayerArray[layerIndex]->m_IKPass;
+}
+
+// lazy search to find the right evaluation context for the given clip
+static bool FindClipInController(AnimationClip &clip, AnimationClipVector const& clips, AnimationSetBindings const& animationSetBindings, int &layerIndex, int &clipLayerIndex)
+{
+ layerIndex = -1;
+ clipLayerIndex = -1;
+
+
+ int clipIndex = -1;
+ for(int clipIter = 0; clipIndex == -1 && clipIter < clips.size(); clipIter++)
+ {
+ AnimationClip* currentClip = clips[clipIter];
+ if (&clip == currentClip)
+ {
+ clipIndex = clipIter;
+ break;
+ }
+ }
+
+ if (clipIndex == -1)
+ return false;
+
+ for(int layerIter = 0; layerIndex == -1 && layerIter < animationSetBindings.animationSet->m_LayerCount; layerIter++)
+ {
+ for(int clipLayerIter = 0; clipLayerIndex == -1 && clipLayerIter < animationSetBindings.animationSet->m_ClipPerLayer[layerIter]; clipLayerIter++)
+ {
+ const mecanim::animation::AnimationSet::Clip& clip = animationSetBindings.animationSet->m_ClipConstant[layerIter][clipLayerIter];
+ if (clip.m_ClipIndex == clipIndex && clip.m_Clip != NULL)
+ {
+ layerIndex = layerIter;
+ clipLayerIndex = clipLayerIter;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+namespace
+{
+ mecanim::animation::ControllerConstant* BuildController(AnimationClip const& clip, mecanim::memory::Allocator& allocator)
+ {
+ mecanim::uint32_t id = mecanim::processCRC32(clip.GetName());
+ mecanim::animation::BlendTreeConstant* blendTreeCst = mecanim::animation::CreateBlendTreeConstant(id, allocator);
+ mecanim::statemachine::StateConstant* stateCst = mecanim::statemachine::CreateStateConstant(0, 0, 1, true,false, 0, &blendTreeCst, 1, id, id, 0, true, allocator);
+ mecanim::statemachine::StateMachineConstant* stateMachinCst = CreateStateMachineConstant(&stateCst,1 , 0, 0, 0, 1, allocator);
+ mecanim::animation::LayerConstant* layer = mecanim::animation::CreateLayerConstant(0, 0, allocator);
+ layer->m_BodyMask = mecanim::human::FullBodyMask();
+ layer->m_SkeletonMask = 0;
+
+ mecanim::ValueArrayConstant* values = mecanim::CreateValueArrayConstant(0, 0, allocator);
+ mecanim::ValueArray* defaultValues = mecanim::CreateValueArray(values, allocator);
+
+ return mecanim::animation::CreateControllerConstant(1, &layer,1, &stateMachinCst, values, defaultValues, allocator);
+ }
+}
+
+Animator::AutoMecanimDataSet::~AutoMecanimDataSet()
+{
+ Reset();
+}
+
+void Animator::AutoMecanimDataSet::Reset()
+{
+ if(m_MecanimDataSet.m_AvatarBindingConstant != NULL)
+ UnregisterAvatarBindingObjects(m_MecanimDataSet.m_AvatarBindingConstant);
+ if(m_MecanimDataSet.m_GenericBindingConstant != NULL)
+ UnregisterGenericBindingObjects(m_MecanimDataSet.m_GenericBindingConstant);
+
+ m_MecanimDataSet.Reset();
+ m_Alloc.Reset();
+}
+
+bool Animator::Sample(AnimationClip& clip, float inTime)
+{
+ PROFILER_AUTO(gProfileSample, this);
+
+ if(!clip.IsAnimatorMotion())
+ return NULL;
+
+
+ m_SamplingDataSet.Reset();
+
+ SetupAvatarMecanimDataSet(m_Avatar.IsValid () ? m_Avatar->GetAsset() : NULL, m_SamplingDataSet.m_Alloc, *m_SamplingDataSet);
+
+ // muscle clip can be NULL when there is not curve at all in the clip.
+ mecanim::animation::ClipMuscleConstant* muscleConstant = clip.GetRuntimeAsset();
+ if(muscleConstant == NULL)
+ {
+ // in this case we want to set a human rig into relax pose to start keyframing
+ if(m_SamplingDataSet->m_AvatarConstant->isHuman())
+ {
+ mecanim::human::HumanPose pose;
+
+ mecanim::human::Human const* human = m_SamplingDataSet->m_AvatarConstant->m_Human.Get();
+ mecanim::human::RetargetTo(human, &pose, 0, math::xformIdentity(), m_SamplingDataSet->m_AvatarOutput->m_HumanPoseOutput, m_SamplingDataSet->m_AvatarWorkspace->m_BodySkeletonPoseWs, m_SamplingDataSet->m_AvatarWorkspace->m_BodySkeletonPoseWsA);
+ mecanim::animation::EvaluateAvatarEnd(m_SamplingDataSet->m_AvatarConstant, m_SamplingDataSet->m_AvatarInput, m_SamplingDataSet->m_AvatarOutput, m_SamplingDataSet->m_AvatarMemory, m_SamplingDataSet->m_AvatarWorkspace, NULL);
+
+ SetHumanTransformPropertyValues (*m_SamplingDataSet->m_AvatarBindingConstant, *m_SamplingDataSet->m_AvatarOutput->m_SkeletonPoseOutput);
+ SetTransformPropertyApplyMainThread (GetComponent (Transform), *m_SamplingDataSet->m_AvatarBindingConstant, false); // when sampling we don't skip apply root
+ }
+ return false;
+ }
+
+
+ AnimationClipVector clips;
+ clips.push_back( PPtr<AnimationClip>(&clip) );
+
+ mecanim::animation::ControllerConstant const* controllerConstant = ::BuildController(clip, m_SamplingDataSet.m_Alloc);
+ UnityEngine::Animation::AnimationSetBindings const* animationSetBindings = UnityEngine::Animation::CreateAnimationSetBindings(controllerConstant, clips, m_SamplingDataSet.m_Alloc);
+
+ SetupControllerMecanimDataSet(controllerConstant, animationSetBindings, m_SamplingDataSet.m_Alloc, *m_SamplingDataSet);
+
+ int layerIndex = 0;
+ int clipLayerIndex = 0;
+
+ // prepare the evaluation context for clip and evaluate in float array
+ mecanim::ValueArray* valuesDefault = m_SamplingDataSet->m_GenericBindingConstant->controllerBindingConstant->m_DynamicValuesDefault;
+ mecanim::ValueArrayConstant* valuesDefaultConstant = m_SamplingDataSet->m_GenericBindingConstant->controllerBindingConstant->m_DynamicValuesConstant;
+ mecanim::ValueArrayMask& readMask = *m_SamplingDataSet->m_AvatarWorkspace->m_ControllerWorkspace->m_ReadMask;
+
+ mecanim::animation::AnimationSet::Clip* setClip = &animationSetBindings->animationSet->m_ClipConstant[layerIndex][clipLayerIndex];
+ mecanim::animation::ClipMemory* clipMemory = m_SamplingDataSet->m_AnimationSetMemory->m_ClipMemory[layerIndex][clipLayerIndex];
+ mecanim::animation::ClipOutput* clipOutput = m_SamplingDataSet->m_AnimationSetMemory->m_ClipOutput;
+
+ mecanim::animation::ClipInput in;
+ in.m_Time = inTime;
+
+ mecanim::animation::EvaluateClip (muscleConstant->m_Clip.Get(), &in, clipMemory, clipOutput);
+
+ // load values and retarget
+ mecanim::SetValueMask (&readMask, false);
+ ValuesFromClip (*valuesDefault, *muscleConstant, *clipOutput, setClip->m_Bindings, animationSetBindings->animationSet->m_IntegerRemapStride, *m_SamplingDataSet->m_AvatarOutput->m_DynamicValuesOutput, readMask);
+ if (m_SamplingDataSet->m_AvatarConstant->isHuman())
+ {
+ mecanim::animation::GetHumanPose (*muscleConstant,clipOutput->m_Values,*m_SamplingDataSet->m_AvatarOutput->m_HumanPoseBaseOutput);
+ mecanim::human::HumanPoseCopy (*m_SamplingDataSet->m_AvatarOutput->m_HumanPoseOutput,*m_SamplingDataSet->m_AvatarOutput->m_HumanPoseBaseOutput);
+ mecanim::animation::EvaluateAvatarRetarget (m_SamplingDataSet->m_AvatarConstant, m_SamplingDataSet->m_AvatarInput, m_SamplingDataSet->m_AvatarOutput, m_SamplingDataSet->m_AvatarMemory, m_SamplingDataSet->m_AvatarWorkspace, m_SamplingDataSet->m_ControllerConstant);
+ mecanim::animation::EvaluateAvatarEnd (m_SamplingDataSet->m_AvatarConstant, m_SamplingDataSet->m_AvatarInput, m_SamplingDataSet->m_AvatarOutput, m_SamplingDataSet->m_AvatarMemory, m_SamplingDataSet->m_AvatarWorkspace, m_SamplingDataSet->m_ControllerConstant);
+ }
+
+ // Controller animated values
+ ValueArrayCopy (valuesDefaultConstant, m_SamplingDataSet->m_AvatarOutput->m_DynamicValuesOutput, m_SamplingDataSet->m_ControllerConstant->m_Values.Get(), m_SamplingDataSet->m_AvatarMemory->m_ControllerMemory->m_Values.Get(), animationSetBindings->animationSet->m_AdditionalIndexArray);
+
+ // set transform values
+ if (m_SamplingDataSet->m_AvatarConstant->isHuman())
+ SetHumanTransformPropertyValues (*m_SamplingDataSet->m_AvatarBindingConstant, *m_SamplingDataSet->m_AvatarOutput->m_SkeletonPoseOutput);
+ SetGenericTransformPropertyValues (*m_SamplingDataSet->m_GenericBindingConstant, *m_SamplingDataSet->m_AvatarOutput->m_DynamicValuesOutput,0); // when sampling set root transform if needed
+
+ SetTransformPropertyApplyMainThread (GetComponent (Transform), *m_SamplingDataSet->m_GenericBindingConstant, *m_SamplingDataSet->m_AvatarBindingConstant, false); // when sampling we don't skip apply root
+
+ // set generic values
+ SetGenericFloatPropertyValues (*m_SamplingDataSet->m_GenericBindingConstant, *m_SamplingDataSet->m_AvatarOutput->m_DynamicValuesOutput);
+ SetGenericPPtrPropertyValues (*m_SamplingDataSet->m_GenericBindingConstant, *m_SamplingDataSet->m_AvatarOutput->m_DynamicValuesOutput);
+
+ return true;
+}
+
+void Animator::UpdateAvatars (Animator** inputAvatars, size_t inputSize, float deltaTime, bool doFKMove, bool doRetargetIKWrite)
+{
+ PROFILER_AUTO(gAnimatorUpdate, NULL)
+
+ #if ENABLE_MULTITHREADED_ANIMATION
+
+ JobScheduler& scheduler = GetJobScheduler();
+ JobScheduler::JobGroupID jobGroup;
+
+ #define AVATAR_LOOP(x,list,profile) \
+ { \
+ PROFILER_AUTO(profile, NULL); \
+ size_t avatarJobCount = list.size(); \
+ jobGroup = scheduler.BeginGroup(avatarJobCount); \
+ for (size_t i = 0; i < avatarJobCount; ++i) \
+ { Animator& avatar = *list[i]; scheduler.SubmitJob (jobGroup, x, &avatar, NULL); } \
+ scheduler.WaitForGroup (jobGroup); \
+ }
+ #else
+
+ #define AVATAR_LOOP(x,list, profile) \
+ for (size_t i=0;i<list.size();i++) \
+ { Animator& avatar = *list[i]; PROFILER_AUTO(profile, &avatar); x (&avatar); }
+
+ #endif
+
+ if(doFKMove)
+ {
+ // invisible & visible animators
+ dynamic_array<Animator*> activeAvatars (kMemTempAlloc);
+ activeAvatars.reserve(inputSize);
+
+ for (size_t i=0;i<inputSize;i++)
+ {
+ Animator& avatar = *inputAvatars[i];
+
+ if (avatar.Prepare())
+ {
+ activeAvatars.push_back(&avatar);
+ }
+ }
+
+ // Init delta time
+ for (size_t i=0;i<activeAvatars.size();i++)
+ {
+ Animator& avatar = *activeAvatars[i];
+ avatar.InitStep(deltaTime);
+ }
+
+
+ // Multithreaded FK Step
+ AVATAR_LOOP(FKStepStatic, activeAvatars, gProfileFKStep)
+
+ // MainThread AnimationEvents and OnAnimatorMove script call
+ for (size_t i=0;i<activeAvatars.size();i++)
+ {
+ Animator& avatar = *activeAvatars[i];
+ avatar.FireAnimationEvents();
+ avatar.ApplyOnAnimatorMove();
+ }
+ }
+
+ if (doRetargetIKWrite)
+ {
+ int layerCount = 0;
+
+ // only visible animators
+ dynamic_array<Animator*> visibleAvatars (kMemTempAlloc);
+ visibleAvatars.reserve(inputSize);
+
+ // human visible animators
+ dynamic_array<Animator*> humanAvatars (kMemTempAlloc);
+ humanAvatars.reserve(inputSize);
+
+ for (size_t i=0;i<inputSize;i++)
+ {
+ Animator& avatar = *inputAvatars[i];
+
+ if (avatar.Prepare() && avatar.m_Visible)
+ {
+ layerCount = avatar.GetLayerCount() > layerCount ? avatar.GetLayerCount() : layerCount;
+ visibleAvatars.push_back(&avatar);
+
+ if(avatar.IsHuman())
+ {
+ humanAvatars.push_back(&avatar);
+
+ avatar.m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoIK = true;
+ }
+
+ avatar.m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoWrite = true;
+ }
+ }
+
+
+ // Multithreaded Retarget & PrepareIK
+ AVATAR_LOOP(RetargetStepStatic, humanAvatars, gProfileRetarget)
+
+ // Default IK Pass
+ AVATAR_LOOP(AvatarIKAndEndStepStatic, humanAvatars, gProfileAvatarIK)
+ AVATAR_LOOP(AvatarWriteStepStatic, humanAvatars, gProfileAvatarWrite)
+
+ // Layered IK Pass
+ for(int layerIter = 0; layerIter < layerCount; layerIter++)
+ {
+ // MainThread OnApplyAvatarIK
+ for (size_t i = 0; i < humanAvatars.size(); i++)
+ {
+ Animator& avatar = *humanAvatars[i];
+
+ bool ikPass = DoesLayerHaveIKPass (layerIter, *avatar.m_EvaluationDataSet.m_ControllerConstant);
+ if (ikPass)
+ {
+ avatar.ApplyOnAnimatorIK (layerIter);
+ }
+
+ avatar.m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoIK = ikPass;
+ avatar.m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoWrite = ikPass;
+ }
+
+ AVATAR_LOOP(AvatarIKAndEndStepStatic,humanAvatars, gProfileAvatarIK)
+ AVATAR_LOOP(AvatarWriteStepStatic, humanAvatars, gProfileAvatarWrite)
+ }
+
+ // Write to those that were not written already
+ AVATAR_LOOP(AvatarWriteStepStatic, visibleAvatars, gProfileAvatarWrite)
+
+ // MainThread Apply skeleton to transform components
+ for (size_t i=0;i<visibleAvatars.size();i++)
+ {
+ Animator& animator = *visibleAvatars[i];
+ if(animator.IsActive()) // animator can be turned Inactive by a parent animator -> 566794
+ {
+ {
+ if (animator.m_HasTransformHierarchy)
+ {
+ PROFILER_AUTO(gAnimatorSendTransformChanged, &animator)
+ SetTransformPropertyApplyMainThread (animator.GetComponent(Transform), *animator.m_EvaluationDataSet.m_GenericBindingConstant, *animator.m_EvaluationDataSet.m_AvatarBindingConstant, animator.IsHuman() || animator.HasRootMotion()); // if it has root motion we skip root
+ }
+ else
+ {
+ const mecanim::skeleton::SkeletonPose* pose = animator.GetGlobalSpaceSkeletonPose();
+ if (pose && animator.m_EvaluationDataSet.m_AvatarConstant)
+ SetFlattenedSkeletonTransformsMainThread (*animator.m_EvaluationDataSet.m_AvatarBindingConstant, *pose, *animator.m_EvaluationDataSet.m_AvatarConstant);
+ }
+ }
+
+ {
+
+ PROFILER_AUTO(gAnimatorSetGenericProperties, &animator)
+ SetGenericFloatPropertyValues (*animator.m_EvaluationDataSet.m_GenericBindingConstant, *animator.m_EvaluationDataSet.m_AvatarOutput->m_DynamicValuesOutput);
+ SetGenericPPtrPropertyValues (*animator.m_EvaluationDataSet.m_GenericBindingConstant, *animator.m_EvaluationDataSet.m_AvatarOutput->m_DynamicValuesOutput);
+ }
+
+ animator.Record(deltaTime);
+
+ UInt8 deltaTimeIs0 = (deltaTime==0) ? 1 : 0;
+ animator.m_EvaluationDataSet.m_AvatarMemory->m_FirstEval &= deltaTimeIs0;
+ }
+ }
+ }
+}
+
+bool Animator::Prepare()
+{
+ if (!IsInitialize())
+ CreateObject();
+
+ return IsInitialize();
+}
+
+void Animator::InitStep(float deltaTime)
+{
+ if(IsAutoPlayingBack())
+ {
+ SetPlaybackTimeInternal(m_AvatarPlayback.CursorTime() + (deltaTime * GetSpeed()));
+ }
+ else
+ {
+ m_EvaluationDataSet.m_AvatarInput->m_DeltaTime = deltaTime * GetSpeed();
+ }
+
+ if(IsPlayingBack())
+ {
+ m_EvaluationDataSet.m_AvatarInput->m_DeltaTime = m_PlaybackDeltaTime;
+ m_PlaybackDeltaTime = 0;
+ }
+}
+
+void Animator::EvaluateSM()
+{
+ if(Prepare())
+ {
+ mecanim::animation::EvaluateAvatarSM(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_ControllerConstant);
+}
+}
+
+void Animator::FKStep()
+{
+ m_DeltaPosition = Vector3f::zero;
+ m_DeltaRotation = Quaternionf::identity();
+
+ {
+ PROFILER_AUTO_DETAIL (gProfileDetailSM, this)
+ mecanim::animation::EvaluateAvatarSM(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_ControllerConstant);
+ }
+
+ {
+ PROFILER_AUTO_DETAIL(gProfileDetailFK, this);
+
+ mecanim::animation::EvaluateAvatarLayers(m_EvaluationDataSet.m_GenericBindingConstant->controllerBindingConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_AnimationSetMemory);
+
+ if(m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ {
+ // Prepare for OnAnimatorMove
+ m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_TargetX.t *= math::float1(m_EvaluationDataSet.m_AvatarConstant->m_Human->m_Scale);
+
+ TargetMatch(m_MustCompleteMatch);
+ m_MustCompleteMatch = false;
+
+ m_DeltaPosition = RotateVectorByQuat(GetAvatarRotation(), float4ToVector3f(m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX.t*m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.s*math::float1(m_EvaluationDataSet.m_AvatarConstant->m_Human->m_Scale)));
+ m_DeltaRotation = float4ToQuaternionf(m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX.q);
+ }
+ else if(m_EvaluationDataSet.m_AvatarConstant->m_RootMotionBoneIndex != -1)
+ {
+ m_DeltaPosition = RotateVectorByQuat(GetAvatarRotation(), float4ToVector3f(m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX.t*m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.s));
+ m_DeltaRotation = float4ToQuaternionf(m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX.q);
+ }
+
+ if(m_EvaluationDataSet.m_AvatarConstant->isHuman() || m_EvaluationDataSet.m_AvatarConstant->m_RootMotionBoneIndex != -1)
+ {
+ mecanim::animation::EvaluateAvatarX(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace);
+ }
+
+ if(m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ {
+ m_TargetPosition = float4ToVector3f(math::xformMulVec(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX, m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_TargetX.t));
+ m_TargetRotation = float4ToQuaternionf(math::normalize(math::quatMul(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.q, m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_TargetX.q)));
+ m_PivotPosition = float4ToVector3f(math::xformMulVec(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX, m_EvaluationDataSet.m_AvatarMemory->m_Pivot));
+ }
+ }
+}
+
+void Animator::RetargetStep()
+{
+ mecanim::animation::EvaluateAvatarRetarget(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_ControllerConstant);
+}
+
+void Animator::AvatarIKAndEndStep()
+{
+ if (!m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoIK)
+ return;
+
+ {
+ PROFILER_AUTO_DETAIL(gProfileDetailAvatarIK, this)
+ mecanim::animation::EvaluateAvatarIK(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_ControllerConstant);
+ }
+
+ {
+ PROFILER_AUTO_DETAIL(gProfileDetailAvatarEnd, this)
+
+ mecanim::animation::EvaluateAvatarEnd(m_EvaluationDataSet.m_AvatarConstant, m_EvaluationDataSet.m_AvatarInput, m_EvaluationDataSet.m_AvatarOutput, m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarWorkspace, m_EvaluationDataSet.m_ControllerConstant);
+ }
+}
+
+void Animator::AvatarWriteStep()
+{
+ if (!m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoWrite)
+ return;
+
+ m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_DoWrite = false;
+
+ const bool hasRootMotion = m_EvaluationDataSet.m_AvatarConstant->isHuman() || (m_EvaluationDataSet.m_AvatarConstant->m_RootMotionBoneIndex != -1) ;
+
+ if (m_HasTransformHierarchy)
+ {
+ // Write Transforms from humanoid skeleton
+ if (m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ /// review this function to just write what it needs
+ SetHumanTransformPropertyValues (*m_EvaluationDataSet.m_AvatarBindingConstant, *m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput);
+
+ // Write Transforms for generic binding
+ SetGenericTransformPropertyValues (*m_EvaluationDataSet.m_GenericBindingConstant, *m_EvaluationDataSet.m_AvatarOutput->m_DynamicValuesOutput, hasRootMotion ? &GetComponent(Transform) : 0);
+ }
+ else if (m_EvaluationDataSet.m_AvatarConstant->m_AvatarSkeleton->m_Count > 0)
+ {
+ mecanim::animation::SkeletonPoseFromValue( *m_EvaluationDataSet.m_AvatarConstant->m_AvatarSkeleton.Get(),
+ *m_EvaluationDataSet.m_AvatarConstant->m_AvatarSkeletonPose.Get(),
+ *m_EvaluationDataSet.m_AvatarOutput->m_DynamicValuesOutput,
+ m_EvaluationDataSet.m_GenericBindingConstant->controllerBindingConstant->m_SkeletonTQSMap,
+ *m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput,
+ m_EvaluationDataSet.m_AvatarConstant->isHuman() ? m_EvaluationDataSet.m_AvatarConstant->m_HumanSkeletonReverseIndexArray.Get() : 0,
+ hasRootMotion );
+
+ if (!m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ {
+ m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput->m_X[0] = m_EvaluationDataSet.m_AvatarMemory->m_AvatarX;
+ }
+
+ mecanim::skeleton::SkeletonPoseComputeGlobal (m_EvaluationDataSet.m_AvatarConstant->m_AvatarSkeleton.Get(), m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput, m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput);
+
+ m_EvaluationDataSet.m_AvatarMemory->m_SkeletonPoseOutputReady = true;
+ }
+}
+
+const mecanim::skeleton::SkeletonPose* Animator::GetGlobalSpaceSkeletonPose() const
+{
+ if (m_EvaluationDataSet.m_AvatarConstant && m_EvaluationDataSet.m_AvatarMemory->m_SkeletonPoseOutputReady)
+ return m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput;
+ return NULL;
+}
+
+void Animator::ApplyOnAnimatorMove()
+{
+ if(!IsPlayingBack())
+ {
+ if (SupportsOnAnimatorMove ())
+ {
+ SendMessage (kAnimatorMove);
+ }
+ else
+ {
+ if (m_ApplyRootMotion)
+ {
+ ApplyBuiltinRootMotion();
+ }
+ }
+
+ // force feedback of transform in avatarX
+ // avatarX is always modified by deltaTransform in FK step
+ // if transform is not modified then avatarX won't be sync with it
+ // case 498101
+ // TODO: we could skip this most of the time with a simple dirty check
+ Transform& avatarTransform = GetComponent(Transform);
+ SetAvatarPosition(avatarTransform.GetPosition());
+ SetAvatarRotation(avatarTransform.GetRotation());
+ }
+ else
+ {
+ Transform& avatarTransform = GetComponent(Transform);
+ avatarTransform.SetPositionAndRotation(GetAvatarPosition(), GetAvatarRotation());
+ }
+}
+
+void Animator::ApplyBuiltinRootMotion()
+{
+ PROFILER_AUTO(gProfileApplyRootMotion, this);
+
+ // Builtin move (For example rigidbodies & Character Controllers)
+ RootMotionData motionData;
+ motionData.deltaPosition = GetDeltaPosition();
+ motionData.targetRotation = GetAvatarRotation();
+ motionData.gravityWeight = GetGravityWeight();
+ motionData.didApply = false;
+ SendMessage (kAnimatorMoveBuiltin, &motionData, ClassID(RootMotionData));
+
+ // Fallback to just moving the transform
+ if (!motionData.didApply)
+ {
+ Transform& avatarTransform = GetComponent(Transform);
+ avatarTransform.SetPositionAndRotation(GetAvatarPosition(), motionData.targetRotation);
+ }
+}
+
+void Animator::FireAnimationEvents()
+{
+ if(!m_FireEvents) return; // we dont want to fire events in previewers
+
+ AnimationClipVector const allClips = GetAnimationClips();
+ for(int i = 0; i < m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_BlendingClipCount; i++)
+ {
+ mecanim::animation::BlendingClip &blendingClip = m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_BlendingClipArray[i];
+
+ AnimationClip *clip = allClips[blendingClip.m_ClipIndex];
+
+ if (clip->HasAnimationEvents())
+ {
+ float prevTime = blendingClip.m_Reverse ? blendingClip.m_Time : blendingClip.m_PrevTime;
+ float time = blendingClip.m_Reverse ? blendingClip.m_PrevTime : blendingClip.m_Time;
+
+ clip->FireAnimationEvents (prevTime, time, *this);
+ }
+ }
+}
+
+bool Animator::SupportsOnAnimatorMove ()
+{
+ return GetGameObject().GetSupportedMessages() & kHasOnAnimatorMove;
+}
+
+void Animator::ApplyOnAnimatorIK(int layerIndex)
+{
+ if (GetGameObject().GetSupportedMessages() & kHasOnAnimatorIK)
+ SendMessage (kAnimatorIK, layerIndex, ClassID(int));
+}
+
+void Animator::Record(float deltaTime)
+{
+ if(m_RecorderMode == eRecord && GetSpeed() >= 0)
+ {
+ m_AvatarPlayback.RecordFrame(deltaTime*GetSpeed(), m_EvaluationDataSet.m_AvatarMemory);
+ }
+}
+
+void* Animator::FKStepStatic (void* userData)
+{
+ PROFILER_AUTO(gProfileFKStep,NULL);
+ static_cast<Animator*> (userData)->FKStep();
+ return NULL;
+}
+
+void* Animator::RetargetStepStatic (void* userData)
+{
+ PROFILER_AUTO(gProfileRetarget,NULL);
+ static_cast<Animator*> (userData)->RetargetStep();
+ return NULL;
+}
+
+void* Animator::AvatarIKAndEndStepStatic (void* userData)
+{
+ PROFILER_AUTO(gProfileAvatarIK,NULL);
+ static_cast<Animator*> (userData)->AvatarIKAndEndStep();
+ return NULL;
+}
+
+void* Animator::AvatarWriteStepStatic (void* userData)
+{
+ PROFILER_AUTO(gProfileAvatarWrite,NULL);
+ static_cast<Animator*> (userData)->AvatarWriteStep();
+ return NULL;
+}
+
+void Animator::Update(float deltaTime)
+{
+ Animator* avatar = this;
+ UpdateAvatars (&avatar, 1, deltaTime, true, true);
+}
+
+bool Animator::IsAvatarInitialize() const
+{
+ return m_EvaluationDataSet.m_AvatarMemory;
+}
+
+bool Animator::IsInitialize() const
+{
+ return IsAvatarInitialize() && m_EvaluationDataSet.m_ControllerMemory;
+}
+
+void Animator::SetLayersAffectMassCenter(bool value)
+{
+ if(!IsAvatarInitialize())
+ return;
+
+ m_EvaluationDataSet.m_AvatarInput->m_LayersAffectMassCenter = value;
+}
+
+bool Animator::GetLayersAffectMassCenter() const
+{
+ if(!IsAvatarInitialize())
+ return false;
+
+ return m_EvaluationDataSet.m_AvatarInput->m_LayersAffectMassCenter ;
+}
+
+bool Animator::IsInManagerList() const
+{
+ return m_BehaviourIndex != -1 || m_FixedBehaviourIndex != -1;
+}
+
+bool Animator::IsValid() const
+{
+ return m_Controller.GetInstanceID() != 0;
+}
+
+
+RuntimeAnimatorController* Animator::GetRuntimeAnimatorController() const
+{
+ return m_Controller;
+}
+
+void Animator::SetRuntimeAnimatorController(RuntimeAnimatorController* controller)
+{
+ if(m_Controller != PPtr<RuntimeAnimatorController>(controller))
+ {
+ m_Controller = controller;
+
+ UpdateInManager();
+
+ CreateObject();
+ SetDirty();
+ }
+}
+
+
+AnimatorController* Animator::GetAnimatorController()const
+{
+ return dynamic_pptr_cast<AnimatorController*>(m_Controller);
+}
+
+
+AnimatorOverrideController* Animator::GetAnimatorOverrideController()const
+{
+ return dynamic_pptr_cast<AnimatorOverrideController*>(m_Controller);
+}
+
+void Animator::UpdateInManager()
+{
+ bool isValid = IsValid() && IsAddedToManager() ;
+
+ bool isEffectivelyAddedToManager = IsInManagerList();
+
+ if(!isValid && isEffectivelyAddedToManager)
+ {
+ RemoveFromManager();
+ }
+ else if(isValid && !isEffectivelyAddedToManager)
+ {
+ AddToManager();
+ }
+}
+
+
+
+
+Avatar* Animator::GetAvatar()
+{
+ return m_Avatar;
+}
+
+void Animator::SetAvatar(Avatar* avatar)
+{
+ if(m_Avatar != PPtr<Avatar>(avatar))
+ {
+ m_Avatar = avatar;
+
+ UpdateInManager();
+
+ CreateObject();
+ SetDirty();
+ }
+}
+
+const mecanim::animation::AvatarConstant* Animator::GetAvatarConstant()
+{
+ if (!IsAvatarInitialize())
+ CreateObject();
+ return m_EvaluationDataSet.m_AvatarConstant;
+}
+
+
+void Animator::SetCullingMode (CullingMode mode)
+{
+ if (m_CullingMode == mode)
+ return;
+
+ m_CullingMode = mode;
+
+ InitializeVisibilityCulling ();
+}
+
+void Animator::SetVisibleRenderers(bool visible)
+{
+ Assert(m_CullingMode == kCullBasedOnRenderers);
+
+ bool becameVisible = visible && !m_Visible;
+ m_Visible = visible;
+
+ if (!IsWorldPlaying())
+ return;
+
+ // Perform Retarget & IK step during culling to ensure the animator
+ // will be rendered correctly in the same frame.
+ if (becameVisible )
+ {
+ float deltaTime = GetDeltaTime();
+
+ if(!IsInManagerList() || deltaTime == 0)
+ return;
+
+ if(Prepare())
+ {
+ Animator* animator[1] = { this };
+
+ bool firstEval = m_EvaluationDataSet.m_AvatarMemory->m_FirstEval;
+ m_EvaluationDataSet.m_AvatarMemory->m_FirstEval = 1;
+ m_EvaluationDataSet.m_AvatarMemory->m_SkeletonPoseOutputReady = 0;
+ UpdateAvatars (animator, 1, deltaTime, firstEval, true);
+ }
+ }
+}
+
+void Animator::AnimatorVisibilityCallback (void* userData, void* renderer, int visibilityEvent)
+{
+ Animator& animator = *reinterpret_cast<Animator*> (userData);
+
+ if (visibilityEvent == kBecameVisibleEvent)
+ animator.SetVisibleRenderers(true);
+ else if (visibilityEvent == kBecameInvisibleEvent)
+ {
+ animator.SetVisibleRenderers(animator.IsAnyRendererVisible ());
+ }
+ else if (visibilityEvent == kWillDestroyEvent)
+ {
+ animator.RemoveContainedRenderer(renderer);
+
+ // Check if we are visible
+ animator.SetVisibleRenderers(animator.IsAnyRendererVisible ());
+ }
+}
+
+void Animator::RemoveContainedRenderer (void* renderer)
+{
+ for (int i=0;i<m_ContainedRenderers.size();i++)
+ {
+ if (m_ContainedRenderers[i] == renderer)
+ {
+ m_ContainedRenderers[i] = m_ContainedRenderers.back();
+ m_ContainedRenderers.pop_back();
+ return;
+ }
+ }
+}
+
+bool Animator::IsAnyRendererVisible () const
+{
+ Assert(m_CullingMode == kCullBasedOnRenderers);
+
+ ContainedRenderers::const_iterator end = m_ContainedRenderers.end();
+ for (ContainedRenderers::const_iterator i = m_ContainedRenderers.begin();i != end;++i)
+ {
+ const Renderer* renderer = *i;
+ Assert(renderer->HasEvent(AnimatorVisibilityCallback, this));
+
+ if (renderer->IsVisibleInScene())
+ return true;
+ }
+
+ return false;
+}
+
+void Animator::ClearContainedRenderers ()
+{
+ ContainedRenderers::iterator end = m_ContainedRenderers.end();
+ for (ContainedRenderers::iterator i = m_ContainedRenderers.begin();i != end;++i)
+ {
+ Renderer* renderer = *i;
+ renderer->RemoveEvent(AnimatorVisibilityCallback, this);
+ }
+ m_ContainedRenderers.clear();
+}
+
+void Animator::RecomputeContainedRenderersRecurse (Transform& transform)
+{
+
+ Renderer* renderer = transform.QueryComponent(Renderer);
+ if (renderer)
+ {
+ m_ContainedRenderers.push_back(renderer);
+ renderer->AddEvent(AnimatorVisibilityCallback, this);
+ }
+ Transform::iterator end = transform.end();
+ for (Transform::iterator i = transform.begin();i != end;++i)
+ {
+ RecomputeContainedRenderersRecurse(**i);
+ }
+}
+
+void Animator::InitializeVisibilityCulling ()
+{
+ if(!IsActive())
+ return;
+
+ ClearContainedRenderers ();
+
+ if (m_CullingMode == kCullBasedOnRenderers)
+ {
+ Transform& transform = GetComponent (Transform);
+ RecomputeContainedRenderersRecurse(transform);
+
+ if (m_ContainedRenderers.empty())
+ m_Visible = IsAnyRendererVisible();
+ }
+ else
+ {
+ m_Visible = true;
+ }
+}
+
+
+void Animator::ClearObject()
+{
+ InvokeEvent(kAnimatorClearEvent);
+
+ mecanim::animation::DestroyAnimationSetMemory(m_EvaluationDataSet.m_AnimationSetMemory, mAlloc);
+ mecanim::animation::DestroyControllerMemory(m_EvaluationDataSet.m_ControllerMemory, mAlloc);
+ DestroyAnimatorGenericBindings (m_EvaluationDataSet.m_GenericBindingConstant, mAlloc);
+ DestroyAvatarBindingConstant (m_EvaluationDataSet.m_AvatarBindingConstant, mAlloc);
+ if(m_EvaluationDataSet.m_AvatarWorkspace)
+ mecanim::animation::DestroyControllerWorkspace(m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace, mAlloc);
+ mecanim::animation::DestroyAvatarOutput(m_EvaluationDataSet.m_AvatarOutput, mAlloc);
+ mecanim::animation::DestroyAvatarInput(m_EvaluationDataSet.m_AvatarInput, mAlloc);
+ mecanim::animation::DestroyAvatarMemory(m_EvaluationDataSet.m_AvatarMemory, mAlloc);
+
+ mecanim::animation::DestroyAvatarWorkspace(m_EvaluationDataSet.m_AvatarWorkspace, mAlloc);
+
+ if(m_EvaluationDataSet.m_OwnsAvatar)
+ {
+ mecanim::animation::DestroyAvatarConstant( const_cast<mecanim::animation::AvatarConstant*>(m_EvaluationDataSet.m_AvatarConstant), mAlloc);
+ m_EvaluationDataSet.m_OwnsAvatar = false;
+ }
+
+ m_EvaluationDataSet.m_AvatarConstant = 0;
+ m_EvaluationDataSet.m_AvatarMemory = 0;
+ // It is very important to reset the memory size to zero because we use this member to determine if the data is blobified and ready to be copy by recorder
+ m_EvaluationDataSet.m_AvatarMemorySize = 0;
+ m_EvaluationDataSet.m_AvatarInput = 0;
+ m_EvaluationDataSet.m_AvatarOutput = 0;
+ m_EvaluationDataSet.m_AvatarWorkspace = 0;
+ m_EvaluationDataSet.m_ControllerConstant = 0;
+
+ m_EvaluationDataSet.m_GenericBindingConstant = 0;
+ m_EvaluationDataSet.m_AvatarBindingConstant = 0;
+
+ m_EvaluationDataSet.m_ControllerMemory = 0;
+ m_EvaluationDataSet.m_AnimationSetMemory = 0;
+
+ m_AnimatorControllerNode.Clear();
+ m_AnimatorAvatarNode.Clear();
+
+ m_SamplingDataSet.Reset();
+}
+
+void Animator::SetupAvatarMecanimDataSet(mecanim::animation::AvatarConstant const* avatarConstant, mecanim::memory::Allocator& allocator, Animator::MecanimDataSet& outMecanimDataSet)
+{
+ PROFILER_AUTO(gProfileSetupDataSet, this);
+
+ outMecanimDataSet.m_AvatarConstant = avatarConstant;
+
+ if (outMecanimDataSet.m_AvatarConstant == NULL)
+ {
+ outMecanimDataSet.m_OwnsAvatar = true;
+ outMecanimDataSet.m_AvatarConstant = mecanim::animation::CreateAvatarConstant(0,0,0,0,0,-1,math::xformIdentity(), allocator);
+ }
+
+ // It is very important to reset the memory size to zero because we use this member to determine if the data is blobified and ready to be copy by recorder
+ outMecanimDataSet.m_AvatarMemorySize = 0;
+ outMecanimDataSet.m_AvatarMemory = mecanim::animation::CreateAvatarMemory(outMecanimDataSet.m_AvatarConstant, allocator);
+ outMecanimDataSet.m_AvatarInput = mecanim::animation::CreateAvatarInput(outMecanimDataSet.m_AvatarConstant, allocator);
+ outMecanimDataSet.m_AvatarWorkspace = mecanim::animation::CreateAvatarWorkspace(outMecanimDataSet.m_AvatarConstant, allocator);
+ outMecanimDataSet.m_AvatarOutput = mecanim::animation::CreateAvatarOutput(outMecanimDataSet.m_AvatarConstant, m_HasTransformHierarchy, allocator);
+
+ Transform *effectiveRoot = GetAvatarRoot();
+ if (m_HasTransformHierarchy)
+ outMecanimDataSet.m_AvatarBindingConstant = CreateAvatarBindingConstant (*effectiveRoot, outMecanimDataSet.m_AvatarConstant, allocator);
+ else
+ outMecanimDataSet.m_AvatarBindingConstant = CreateAvatarBindingConstantOpt (*effectiveRoot, outMecanimDataSet.m_AvatarConstant, allocator);
+
+ // Setup AvatarX based on current transform
+ Transform& transform = GetComponent(Transform);
+ SetAvatarPosition(transform.GetPosition());
+ SetAvatarRotation(transform.GetRotation());
+ SetAvatarScale(transform.GetWorldScaleLossy());
+}
+
+void Animator::SetupControllerMecanimDataSet(mecanim::animation::ControllerConstant const* controllerConstant, UnityEngine::Animation::AnimationSetBindings const* animationSetBindings, mecanim::memory::Allocator& allocator, Animator::MecanimDataSet& outMecanimDataSet)
+{
+ outMecanimDataSet.m_ControllerConstant = controllerConstant;
+
+ Transform *effectiveRoot = GetAvatarRoot();
+ if (m_HasTransformHierarchy)
+ outMecanimDataSet.m_GenericBindingConstant = CreateAnimatorGenericBindings (*animationSetBindings, *effectiveRoot, outMecanimDataSet.m_AvatarConstant, outMecanimDataSet.m_ControllerConstant, allocator);
+ else
+ outMecanimDataSet.m_GenericBindingConstant = CreateAnimatorGenericBindingsOpt (*animationSetBindings, *effectiveRoot, outMecanimDataSet.m_AvatarConstant, outMecanimDataSet.m_ControllerConstant, allocator);
+
+ const mecanim::ValueArrayConstant* dynamicValuesConstant = outMecanimDataSet.m_GenericBindingConstant->controllerBindingConstant->m_DynamicValuesConstant;
+
+ outMecanimDataSet.m_ControllerMemory = mecanim::animation::CreateControllerMemory(outMecanimDataSet.m_ControllerConstant, outMecanimDataSet.m_AvatarConstant, animationSetBindings->animationSet, dynamicValuesConstant, allocator);
+ outMecanimDataSet.m_AvatarMemory->m_ControllerMemory = outMecanimDataSet.m_ControllerMemory;
+
+ outMecanimDataSet.m_AnimationSetMemory = mecanim::animation::CreateAnimationSetMemory(animationSetBindings->animationSet, outMecanimDataSet.m_GenericBindingConstant->allowConstantClipSamplingOptimization, allocator);
+
+ outMecanimDataSet.m_AvatarWorkspace->m_ControllerWorkspace = mecanim::animation::CreateControllerWorkspace(outMecanimDataSet.m_ControllerConstant, outMecanimDataSet.m_AvatarConstant, animationSetBindings->animationSet, dynamicValuesConstant, allocator);
+ outMecanimDataSet.m_AvatarOutput->m_DynamicValuesOutput = mecanim::CreateValueArray(dynamicValuesConstant, allocator);
+ outMecanimDataSet.m_AvatarInput->m_GotoStateInfos = allocator.ConstructArray<mecanim::statemachine::GotoStateInfo>(outMecanimDataSet.m_ControllerConstant->m_LayerCount);
+
+ UpdateLeafNodeDuration(*outMecanimDataSet.m_ControllerConstant,*animationSetBindings->animationSet,*outMecanimDataSet.m_ControllerMemory);
+}
+
+void Animator::CreateObject()
+{
+ if(!IsActive())
+ return;
+
+ SET_ALLOC_OWNER(this);
+ PROFILER_AUTO(gAnimatorInitialize, this);
+
+ ClearObject();
+
+ SETPROFILERLABEL(Animator);
+
+ // ///////////////////////////////////////////////////
+ // Setup is split in two part: Avatar and controller
+ // both part are independant
+
+ // ///////////////////////////////////////////////////
+ // 1. Avatar setup and binding
+ mecanim::animation::AvatarConstant* avatarConstant = NULL;
+ if(m_Avatar.IsValid())
+ {
+ avatarConstant = m_Avatar->GetAsset();
+ m_Avatar->AddObjectUser(m_AnimatorAvatarNode);
+ }
+ SetupAvatarMecanimDataSet(avatarConstant, mAlloc, m_EvaluationDataSet);
+
+ // ///////////////////////////////////////////////////
+ // 2. Controller setup and binding
+ if(m_Controller.IsNull())
+ return;
+
+ m_Controller->AddObjectUser(m_AnimatorControllerNode);
+
+ mecanim::animation::ControllerConstant* controllerConstant = m_Controller->GetAsset();
+
+ UnityEngine::Animation::AnimationSetBindings* animationSetBindings = m_Controller->GetAnimationSetBindings();
+ if (animationSetBindings == NULL)
+ return;
+
+ SetupControllerMecanimDataSet(controllerConstant, animationSetBindings, mAlloc, m_EvaluationDataSet);
+}
+
+std::string Animator::GetPerformanceHints()
+{
+ if (m_EvaluationDataSet.m_GenericBindingConstant == NULL)
+ return "Not initialized";
+
+ std::string info;
+ ///@TODO: more accuaracy...
+
+// if (!m_EvaluationDataSet.m_GenericBindingConstant->allowConstantClipSamplingOptimization)
+// info += "Constant curve optimization disabled (Instance default values differ from constant curve values)\n";
+// else
+// info += Format ("Constant curve optimization enabled. %d of %d were eliminated.\n", m_EvaluationDataSet.m_GenericBindingConstant->controllerBindingConstant->m_AnimationSet->m_DynamicReducedValuesConstant->m_Count, GetAnimationSet()->m_DynamicFullValuesConstant->m_Count);
+
+ //@TODO: Bound curves overview like animationclip stats...
+
+ info += "Instance memory: " + FormatBytes (GetRuntimeMemorySize());
+
+ return info;
+}
+
+void Animator::PrepareForPlayback()
+{
+ if(m_EvaluationDataSet.m_AvatarMemorySize == 0)
+ {
+ /// Blobify memory to be able to use inplace allocator during playback
+ mecanim::animation::AvatarMemory *mem = m_EvaluationDataSet.m_AvatarMemory;
+ m_EvaluationDataSet.m_AvatarMemory = CopyBlob(*mem,mAlloc,m_EvaluationDataSet.m_AvatarMemorySize);
+ mecanim::animation::DestroyAvatarMemory(mem, mAlloc);
+ }
+}
+
+void Animator::StartRecording(int frameCount)
+{
+ PrepareForPlayback();
+
+ if(m_RecorderMode == ePlayback)
+ {
+ WarningStringIfLoggingActive("Can't call StartRecording while in playback mode. You must call StopPlayback.");
+ return;
+ }
+
+ m_AvatarPlayback.Init(frameCount);
+ m_RecorderMode = eRecord;
+}
+void Animator::StopRecording()
+{
+ m_RecorderMode = eNormal;
+}
+
+float Animator::GetRecorderStartTime()
+{
+ return m_AvatarPlayback.StartTime();
+}
+
+float Animator::GetRecorderStopTime()
+{
+ return m_AvatarPlayback.StopTime();
+}
+
+void Animator::StartPlayback()
+{
+ if(m_RecorderMode == eRecord)
+ {
+ WarningStringIfLoggingActive("Can't call StartPlayback while in record mode. You must call StopRecording.");
+ return;
+ }
+
+ m_RecorderMode = ePlayback;
+}
+
+
+void Animator::SetPlaybackTimeInternal(float time)
+{
+ float effectiveTime = 0;
+ mecanim::animation::AvatarMemory* memory = m_AvatarPlayback.PlayFrame(time, effectiveTime);
+
+ if(memory != 0 )
+ {
+ if(effectiveTime > time )
+ {
+ if(IsAutoPlayingBack())
+ WarningStringIfLoggingActive ("Cannot rewind Animator Recorder anymore. No more recorded data");
+ else
+ WarningStringIfLoggingActive("Animator Recorder does not have recorded data at given time");
+ }
+ else if(time > m_AvatarPlayback.StopTime())
+ {
+ WarningStringIfLoggingActive("Animator Recorder does not have recorded data at given time, Animator will update based on current AnimatorParameters");
+ }
+
+ PrepareForPlayback();
+
+ m_PlaybackTime = time;
+ mecanim::memory::InPlaceAllocator inPlaceAlloc(m_EvaluationDataSet.m_AvatarMemory, m_EvaluationDataSet.m_AvatarMemorySize);
+ mecanim::animation::AvatarMemory* memoryCopy = CopyBlob(*memory, inPlaceAlloc, m_EvaluationDataSet.m_AvatarMemorySize);
+ if(memoryCopy != 0)
+ m_EvaluationDataSet.m_AvatarMemory = memoryCopy;
+ else
+ {
+ // We don't have enough memory to fulfill the request with the current m_EvaluationDataSet.m_AvatarMemory memory block
+ // Let's try another time with a memory block big enough.
+ mecanim::animation::DestroyAvatarMemory(m_EvaluationDataSet.m_AvatarMemory, mAlloc );
+
+ UInt8* ptr = reinterpret_cast<UInt8*>(mAlloc.Allocate(m_EvaluationDataSet.m_AvatarMemorySize, ALIGN_OF(mecanim::animation::AvatarMemory)));
+ mecanim::memory::InPlaceAllocator inPlaceAlloc(ptr, m_EvaluationDataSet.m_AvatarMemorySize);
+ m_EvaluationDataSet.m_AvatarMemory = CopyBlob(*memory, inPlaceAlloc, m_EvaluationDataSet.m_AvatarMemorySize);
+ if(m_EvaluationDataSet.m_AvatarMemory == 0 )
+ {
+ WarningStringIfLoggingActive ("Can't playback from recorder, cannot allocate memory for recorded data.");
+ m_PlaybackDeltaTime = 0;
+ m_PlaybackTime = 0;
+ return;
+ }
+ }
+ m_PlaybackDeltaTime = time-effectiveTime;
+ }
+ else
+ {
+ WarningStringIfLoggingActive ("Can't playback from recorder, no recorded data found");
+ m_PlaybackDeltaTime = 0;
+ m_PlaybackTime = 0;
+ }
+}
+
+float Animator::GetPlaybackTime()
+{
+
+ if(m_RecorderMode != ePlayback)
+ {
+ WarningStringIfLoggingActive("Can't call GetPlaybackTime while not in playback mode. You must call StartPlayback before.");
+ return -1;
+ }
+
+ return m_PlaybackTime;
+}
+
+void Animator::SetPlaybackTime(float time)
+{
+ if(m_RecorderMode != ePlayback)
+ {
+ WarningStringIfLoggingActive("Can't call SetPlaybackTime while not in playback mode. You must call StartPlayback before.");
+ return ;
+ }
+
+ SetPlaybackTimeInternal(time);
+}
+
+void Animator::StopPlayback()
+{
+ m_RecorderMode = eNormal;
+}
+
+bool Animator::IsOptimizable() const
+{
+ return m_Avatar.IsValid();
+}
+
+bool Animator::IsHuman() const
+{
+ return m_Avatar.IsValid() && m_Avatar->IsHuman();
+}
+
+bool Animator::HasRootMotion() const
+{
+ return m_Avatar.IsValid() && m_Avatar->HasRootMotion();
+}
+
+float Animator::GetHumanScale() const
+{
+ return m_Avatar.IsValid() ? m_Avatar->GetHumanScale() : 1.f;
+}
+
+void Animator::SetApplyRootMotion (bool rootMotion)
+{
+ if (m_ApplyRootMotion != rootMotion)
+ {
+ m_ApplyRootMotion = rootMotion;
+ SetDirty();
+ }
+}
+
+void Animator::SetAnimatePhysics (bool animatePhysics)
+{
+ if (m_AnimatePhysics != animatePhysics)
+ {
+ m_AnimatePhysics = animatePhysics;
+ RemoveFromManager();
+ AddToManager();
+ SetDirty();
+ }
+}
+
+void Animator::SetHasTransformHierarchy (bool value)
+{
+ if (m_HasTransformHierarchy != value)
+ {
+ m_HasTransformHierarchy = value;
+
+ // I need to validate this
+ CreateObject();
+ InitializeVisibilityCulling();
+
+ SetDirty();
+ }
+}
+
+GetSetValueResult Animator::SetFloatDamp(int id, float value, float dampTime, float deltaTime)
+{
+ if (dampTime > 0)
+ {
+ mecanim::dynamics::ScalDamp damper;
+ GetValue(id, damper.m_Value);
+ damper.m_DampTime = dampTime;
+ damper.Evaluate(value, deltaTime);
+
+ return SetValue(id, damper.m_Value);
+ }
+ else
+ {
+ return SetValue(id, value);
+ }
+}
+
+GetSetValueResult Animator::SetFloat(int id, float value)
+{
+ return SetValue(id, value);
+}
+
+GetSetValueResult Animator::SetBool(int id, bool value)
+{
+ return SetValue(id, value);
+}
+
+GetSetValueResult Animator::SetInteger(int id, int value)
+{
+ return SetValue(id, (mecanim::int32_t)value);
+}
+
+GetSetValueResult Animator::GetFloat(int id, float& output)
+{
+ return GetValue(id, output);
+}
+
+GetSetValueResult Animator::GetBool(int id, bool& output)
+{
+ return GetValue(id, output);
+}
+
+GetSetValueResult Animator::GetInteger(int id, int& output)
+{
+ mecanim::int32_t temp;
+ GetSetValueResult res = GetValue(id, temp);
+ output = temp;
+ return res;
+}
+
+GetSetValueResult Animator::ResetTrigger(int id)
+{
+ return SetValue(id, false);
+}
+
+GetSetValueResult Animator::SetTrigger(int id)
+{
+ return SetValue(id, true);
+}
+
+bool Animator::HasParameter(int id)
+{
+ if(!IsInitialize())
+ return false;
+
+ mecanim::int32_t i = mecanim::FindValueIndex(m_EvaluationDataSet.m_ControllerConstant->m_Values.Get(), id);
+ return i != -1;
+}
+
+bool Animator::GetMuscleValue(int id, float *value)
+{
+ *value = 0;
+ if (this->m_Avatar.IsNull() || !this->m_Avatar->IsHuman())
+ return false;
+
+ // it is the desire behavior to return true if the Animator is not initialized correctly because the AnimationWindows is using this function to
+ // find what is the newly added curve type.
+ // Also has a side effect adding any kind of curve like MotionT trigger a ClearObject() on the animator because we are adding new curve to a clip
+ // used by the animator.
+ mecanim::int32_t muscleIndex = mecanim::animation::FindMuscleIndex(id);
+ bool ret = muscleIndex != -1;
+
+ if(m_SamplingDataSet->m_GenericBindingConstant == NULL)
+ return ret;
+
+ if (ret)
+ *value = mecanim::animation::GetMuscleCurveValue(*m_SamplingDataSet->m_AvatarOutput->m_HumanPoseOutput, m_SamplingDataSet->m_AvatarOutput->m_MotionOutput->m_MotionX,muscleIndex);
+
+ return ret;
+}
+
+GetSetValueResult Animator::ParameterControlledByCurve(int id)
+{
+ if (!IsInitialize())
+ return kAnimatorNotInitialized;
+
+ mecanim::int32_t index = mecanim::FindValueIndex(m_EvaluationDataSet.m_ControllerConstant->m_Values.Get(), id);
+ if (index == -1)
+ return kParameterDoesNotExist;
+
+ if (m_EvaluationDataSet.m_GenericBindingConstant->controllerBindingConstant->m_AnimationSet->m_AdditionalIndexArray[index] != -1)
+ return kParameterIsControlledByCurve;
+ else
+ return kGetSetSuccess;
+}
+
+Vector3f Animator::GetAvatarPosition()
+{
+ if(!IsAvatarInitialize())
+ return Vector3f(0,0,0);
+
+ return float4ToVector3f(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.t);
+}
+
+void Animator::SetAvatarPosition(const Vector3f& pos)
+{
+ ///@TODO: Give error messages...
+ if (!IsAvatarInitialize())
+ return ;
+
+ m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.t = Vector3fTofloat4(pos);
+}
+
+Quaternionf Animator::GetAvatarRotation()
+{
+ if(!IsAvatarInitialize())
+ return Quaternionf(0,0,0,1);
+
+
+ return float4ToQuaternionf(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.q);
+}
+
+void Animator::SetAvatarRotation(const Quaternionf& q)
+{
+ if(!IsAvatarInitialize())
+ return;
+
+ m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.q = QuaternionfTofloat4(q);
+}
+
+Vector3f Animator::GetAvatarScale()
+{
+ if(!IsAvatarInitialize())
+ return Vector3f(1,1,1);
+
+ return float4ToVector3f(m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.s);
+}
+
+void Animator::SetAvatarScale(const Vector3f& scale)
+{
+ if (!IsAvatarInitialize())
+ return ;
+
+ m_EvaluationDataSet.m_AvatarMemory->m_AvatarX.s = Vector3fTofloat4(scale,1);
+}
+
+Vector3f Animator::GetDeltaPosition()
+{
+ return m_DeltaPosition;
+}
+
+Quaternionf Animator::GetDeltaRotation()
+{
+ return m_DeltaRotation;
+}
+
+Vector3f Animator::GetBodyPosition()
+{
+ if(!IsAvatarInitialize())
+ return Vector3f();
+
+ mecanim::animation::AvatarConstant const* avatar = m_EvaluationDataSet.m_AvatarConstant;
+
+ if(avatar->isHuman())
+ {
+ return float4ToVector3f(m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.t);
+ }
+
+ return Vector3f();
+}
+
+Quaternionf Animator::GetBodyRotation()
+{
+ if(!IsAvatarInitialize())
+ return Quaternionf();
+
+ mecanim::animation::AvatarConstant const * avatar = m_EvaluationDataSet.m_AvatarConstant;
+
+ if(avatar->isHuman())
+ {
+ return float4ToQuaternionf(m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.q);
+ }
+
+ return Quaternionf();
+}
+
+void Animator::SetBodyPosition(Vector3f const& bodyPosition)
+{
+ if(!IsAvatarInitialize() || !m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ return;
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.t = Vector3fTofloat4(bodyPosition);
+}
+
+void Animator::SetBodyRotation(Quaternionf const& bodyRotation)
+{
+ if(!IsAvatarInitialize() || !m_EvaluationDataSet.m_AvatarConstant->isHuman())
+ return;
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.q = QuaternionfTofloat4(bodyRotation);
+}
+
+Vector3f Animator::GetPivotPosition()
+{
+ return m_PivotPosition;
+}
+
+Vector3f Animator::GetTargetPosition()
+{
+ return m_TargetPosition;
+}
+
+Quaternionf Animator::GetTargetRotation()
+{
+ if (m_EvaluationDataSet.m_AvatarInput->m_TargetIndex >= mecanim::animation::kTargetLeftFoot && m_EvaluationDataSet.m_AvatarInput->m_TargetIndex <= mecanim::animation::kTargetRightHand)
+ {
+ return m_TargetRotation * float4ToQuaternionf(mecanim::human::HumanGetGoalOrientationOffset(mecanim::human::Goal(m_EvaluationDataSet.m_AvatarInput->m_TargetIndex - mecanim::animation::kTargetLeftFoot)));
+ }
+
+ return m_TargetRotation;
+}
+
+float Animator::GetGravityWeight()
+{
+ if(m_EvaluationDataSet.m_AvatarOutput && m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput)
+ {
+ return m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_GravityWeight;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+bool Animator::IsBoneTransform(Transform *transform)
+{
+ if(!IsAvatarInitialize())
+ return false;
+
+ bool ret = false;
+
+ if (m_HasTransformHierarchy)
+ {
+ for(int boneIter = 0; !ret && boneIter < m_EvaluationDataSet.m_AvatarBindingConstant->skeletonBindingsCount; boneIter++)
+ {
+ ret = m_EvaluationDataSet.m_AvatarBindingConstant->skeletonBindings[boneIter] == transform;
+ }
+ }
+ else
+ {
+ for (int exposedIndex = 0; !ret && exposedIndex < m_EvaluationDataSet.m_AvatarBindingConstant->exposedTransformCount; exposedIndex++)
+ {
+ ret = (m_EvaluationDataSet.m_AvatarBindingConstant->exposedTransforms[exposedIndex].transform == transform) &&
+ (m_EvaluationDataSet.m_AvatarBindingConstant->exposedTransforms[exposedIndex].skeletonIndex != -1);
+ }
+ }
+
+ return ret;
+}
+
+Transform* Animator::GetBoneTransform(int humanId)
+{
+ if(!IsAvatarInitialize())
+ return 0;
+
+ Transform *ret = 0;
+
+ mecanim::animation::AvatarConstant* cst = GetAvatar()->GetAsset();
+
+ if(cst && cst->isHuman())
+ {
+ int humanBoneId = HumanTrait::GetBoneId(*GetAvatar(),humanId);
+
+ if( humanBoneId == -1)
+ return NULL;
+
+ if (m_HasTransformHierarchy)
+ {
+ ret = m_EvaluationDataSet.m_AvatarBindingConstant->skeletonBindings[cst->m_HumanSkeletonIndexArray[humanBoneId]];
+ }
+ else
+ {
+ int skeletonIndex = cst->m_HumanSkeletonIndexArray[humanBoneId];
+ for (int exposedIndex = 0; exposedIndex < m_EvaluationDataSet.m_AvatarBindingConstant->exposedTransformCount; exposedIndex++)
+ {
+ const ExposedTransform& exposedTransform = m_EvaluationDataSet.m_AvatarBindingConstant->exposedTransforms[exposedIndex];
+ if (exposedTransform.skeletonIndex == skeletonIndex)
+ {
+ ret = exposedTransform.transform;
+ break;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+void Animator::MatchTarget(Vector3f const& matchPosition, Quaternionf const& matchRotation, int targetIndex, const MatchTargetWeightMask & mask, float startNormalizedTime, float targetNormalizedTime)
+{
+ // It's only possible to match a target on the first layer
+ const int layerIndex = 0;
+
+ if(!ValidateTargetIndex(targetIndex) || IsMatchingTarget() || !IsInitialize())
+ return;
+
+ if(IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1) && IsInTransitionInternal(layerIndex))
+ {
+ WarningStringIfLoggingActive("Calling Animator.MatchTarget while in transition does not have any effect");
+ return;
+ }
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+
+ float internalTime = apStateMachineMem->m_StateMemoryArray[apStateMachineMem->m_CurrentStateIndex]->m_PreviousTime;
+
+ float intTime = 0 ;
+ float currentStateTime = math::modf(internalTime, intTime);
+
+ float effectiveStartTime = 0;
+ float effectiveTargetTime = targetNormalizedTime + intTime;
+
+ if(currentStateTime <= startNormalizedTime)
+ effectiveStartTime = startNormalizedTime + intTime;
+ else if( currentStateTime > startNormalizedTime && currentStateTime < targetNormalizedTime)
+ effectiveStartTime = currentStateTime + intTime;
+ else
+ {
+ // Passed target time, wait for next loop
+ effectiveStartTime = startNormalizedTime + intTime + 1.0f;
+ effectiveTargetTime += 1.0f;
+ }
+
+ AnimatorStateInfo animatorInfo;
+ GetAnimatorStateInfo(layerIndex, true, animatorInfo);
+ if (!animatorInfo.loop != 0 && targetNormalizedTime < effectiveStartTime )
+ return;
+
+ m_MatchTargetMask = mask;
+
+ m_MatchStartTime = effectiveStartTime;
+ m_MatchStateID = animatorInfo.nameHash;
+ m_MatchPosition = matchPosition;
+ m_MatchRotation = SqrMagnitude(matchRotation) > 0 ? matchRotation : Quaternionf::identity();
+ m_EvaluationDataSet.m_AvatarInput->m_TargetIndex = targetIndex;
+ m_EvaluationDataSet.m_AvatarInput->m_TargetTime = effectiveTargetTime < effectiveStartTime ? effectiveTargetTime + 1.f : effectiveTargetTime;
+}
+
+void Animator::InterruptMatchTarget(bool completeMatch)
+{
+ if(completeMatch)
+ m_MustCompleteMatch = true;
+ else
+ {
+ m_MatchStartTime = -1;
+ m_MatchStateID = -1;
+ }
+}
+
+bool Animator::IsMatchingTarget()const
+{
+ if(!IsInitialize())
+ return false;
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[0]->m_StateMachineIndex;
+ mecanim::statemachine::StateMachineConstant const* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+ mecanim::statemachine::StateConstant const* state = apStateMachineConst->m_StateConstantArray[apStateMachineMem->m_CurrentStateIndex].Get();
+
+ return m_MatchStartTime >= 0 && CompareStateID (state, m_MatchStateID);
+}
+
+void Animator::SetTarget(int targetIndex, float targetNormalizedTime)
+{
+ if(!ValidateTargetIndex(targetIndex) || !IsInitialize())
+ return;
+
+ if(IsMatchingTarget())
+ {
+ ErrorString("Calling Animator::SetTarget while already Matching Target does not have any effect");
+ }
+
+ m_EvaluationDataSet.m_AvatarInput->m_TargetIndex = targetIndex;
+ m_EvaluationDataSet.m_AvatarInput->m_TargetTime = targetNormalizedTime;
+}
+
+void Animator::SetSpeed(float speed)
+{
+
+ m_Speed = speed;
+}
+
+float Animator::GetSpeed() const
+{
+ return m_Speed;
+}
+
+bool GetLayerAndStateIndex(mecanim::animation::ControllerConstant const* controllerConstant, mecanim::uint32_t id, int* outLayer, int* outStateIndex)
+{
+ for (int i=0;i < controllerConstant->m_LayerCount;i++)
+ {
+ int index = controllerConstant->m_LayerArray[i]->m_StateMachineIndex;
+
+ // Ignore synced layers
+ if (controllerConstant->m_LayerArray[i]->m_StateMachineMotionSetIndex != 0)
+ continue;
+
+ mecanim::int32_t stateIndex = mecanim::statemachine::GetStateIndex(controllerConstant->m_StateMachineArray[index].Get(), id);
+ if (stateIndex != -1)
+ {
+ *outStateIndex = stateIndex;
+ *outLayer = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+void Animator::GotoState(int layerIndex, int stateId, float normalizedTime, float transitionDuration, float transitionTime)
+{
+ if(!IsInitialize() )
+ return ;
+
+ // Automatically find a good layer index
+ if (layerIndex == -1)
+ {
+ int stateIndex;
+ // StateId = 0 means active state
+ if (stateId == 0)
+ layerIndex = 0;
+ else if (!GetLayerAndStateIndex(m_EvaluationDataSet.m_ControllerConstant, stateId, &layerIndex, &stateIndex))
+ {
+ ErrorString("Animator.GotoState: State could not be found");
+ }
+ }
+
+ if(!ValidateLayerIndex(layerIndex))
+ return;
+
+ const mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+
+ if(stateMachineIndex == -1)
+ return;
+
+ if(stateMachineIndex >= m_EvaluationDataSet.m_ControllerConstant->m_StateMachineCount)
+ {
+ ErrorString("Animator.GotoState: Cannot find statemachine");
+ return;
+ }
+
+ const mecanim::uint32_t motionSetIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineMotionSetIndex;
+ if(motionSetIndex != 0)
+ {
+ ErrorString("Calling Animator.GotoState on Synchronize layer");
+ return;
+ }
+
+ #if DEBUGMODE
+ if (stateId != 0 && mecanim::statemachine::GetStateIndex(m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get(), stateId) == -1)
+ {
+ ErrorString("Animator.GotoState: State could not be found");
+ }
+ #endif
+
+ // When no explicit normalizedTime is specified we will simply start the clip at the start if it is not already playing.
+ if (normalizedTime == -std::numeric_limits<float>::infinity())
+ {
+ AnimatorStateInfo info;
+ GetAnimatorStateInfo(layerIndex, true, info);
+
+ // If the state is not currently playing -> Start Playing it
+ bool nameMatches = info.pathHash == stateId || info.nameHash == stateId;
+ if (nameMatches)
+ return;
+ normalizedTime = 0.0F;
+ }
+
+ m_EvaluationDataSet.m_ControllerMemory->m_StateMachineMemory[stateMachineIndex]->m_ActiveGotoState = true;
+
+ m_EvaluationDataSet.m_AvatarInput->m_GotoStateInfos[layerIndex].m_StateID = stateId;
+ m_EvaluationDataSet.m_AvatarInput->m_GotoStateInfos[layerIndex].m_NormalizedTime = normalizedTime;
+ m_EvaluationDataSet.m_AvatarInput->m_GotoStateInfos[layerIndex].m_TransitionDuration = transitionDuration;
+ m_EvaluationDataSet.m_AvatarInput->m_GotoStateInfos[layerIndex].m_TransitionTime = transitionTime;
+}
+
+#define VALIDATE_IK_GOAL(x) \
+if (!GetBuildSettings().hasAdvancedVersion) \
+ return x; \
+if(!ValidateGoalIndex(index) || !IsAvatarInitialize()) \
+ return x; \
+
+#define VALIDATE_IK_GOAL_VOID() \
+if (!GetBuildSettings().hasAdvancedVersion)\
+ return; \
+if(!ValidateGoalIndex(index) || !IsAvatarInitialize()) \
+ return; \
+
+
+Vector3f Animator::GetGoalPosition(int index)
+{
+ VALIDATE_IK_GOAL(Vector3f::zero)
+
+ return float4ToVector3f(m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_X.t);
+}
+
+void Animator::SetGoalPosition(int index, Vector3f const& pos)
+{
+ VALIDATE_IK_GOAL_VOID()
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_X.t = Vector3fTofloat4(pos);
+}
+
+Quaternionf Animator::GetGoalRotation(int index)
+{
+ VALIDATE_IK_GOAL(Quaternionf::identity())
+
+ return float4ToQuaternionf(math::normalize(math::quatMul(m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_X.q,mecanim::human::HumanGetGoalOrientationOffset(mecanim::human::Goal(index)))));
+}
+
+void Animator::SetGoalRotation(int index, Quaternionf const& rot)
+{
+ VALIDATE_IK_GOAL_VOID()
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_X.q = math::normalize(math::quatMul(QuaternionfTofloat4(rot),math::quatConj(mecanim::human::HumanGetGoalOrientationOffset(mecanim::human::Goal(index)))));
+}
+
+void Animator::SetGoalWeightPosition(int index, float value)
+{
+ VALIDATE_IK_GOAL_VOID()
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_WeightT = value;
+}
+
+void Animator::SetGoalWeightRotation(int index, float value)
+{
+ VALIDATE_IK_GOAL_VOID()
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_WeightR = value;
+}
+
+float Animator::GetGoalWeightPosition(int index)
+{
+ VALIDATE_IK_GOAL(0.0F)
+
+ return m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_WeightT;
+}
+
+float Animator::GetGoalWeightRotation(int index)
+{
+ VALIDATE_IK_GOAL(0.0F)
+
+ return m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[index].m_WeightR;
+}
+
+#define VALIDATE_LOOKAT if (!GetBuildSettings().hasAdvancedVersion || !IsAvatarInitialize()) return;
+
+
+void Animator::SetLookAtPosition(Vector3f lookAtPosition)
+{
+ VALIDATE_LOOKAT
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_LookAtPosition = Vector3fTofloat4(lookAtPosition);
+}
+
+void Animator::SetLookAtClampWeight(float weight)
+{
+ VALIDATE_LOOKAT
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_LookAtWeight.x() = weight;
+}
+
+void Animator::SetLookAtBodyWeight(float weight)
+{
+ VALIDATE_LOOKAT
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_LookAtWeight.y() = weight;
+}
+
+void Animator::SetLookAtHeadWeight(float weight)
+{
+ VALIDATE_LOOKAT
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_LookAtWeight.z() = weight;
+}
+
+void Animator::SetLookAtEyesWeight(float weight)
+{
+ VALIDATE_LOOKAT
+
+ m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_LookAtWeight.w() = weight;
+}
+
+int Animator::GetLayerCount()const
+{
+ if(!IsInitialize())
+ return 0;
+
+ return m_EvaluationDataSet.m_ControllerConstant->m_LayerCount;
+}
+
+std::string Animator::GetLayerName(int layerIndex)
+{
+ if(!ValidateLayerIndex(layerIndex))
+ return "";
+
+ return m_Controller->StringFromID(m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_Binding);
+}
+
+float Animator::GetLayerWeight(int layerIndex)
+{
+ if(!ValidateLayerIndex(layerIndex))
+ return layerIndex == 0 ? 1 : 0 ;
+
+ return m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_LayerWeights[layerIndex];
+}
+
+void Animator::SetLayerWeight(int layerIndex, float w)
+{
+ if(!ValidateLayerIndex(layerIndex))
+ return;
+
+ m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_LayerWeights[layerIndex] = w;
+}
+
+float Animator::GetPivotWeight()
+{
+ if(!IsAvatarInitialize())
+ return 0;
+
+ return m_EvaluationDataSet.m_AvatarMemory->m_PivotWeight;
+}
+
+bool Animator::IsInTransitionInternal(int layerIndex)const
+{
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+
+ if(stateMachineIndex == mecanim::DISABLED_SYNCED_LAYER_IN_NON_PRO)
+ return false;
+
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+ return apStateMachineMem->m_InTransition;
+}
+
+bool Animator::IsInTransition(int layerIndex)const
+{
+ if(!ValidateLayerIndex(layerIndex) )
+ return false;
+
+ return IsInTransitionInternal(layerIndex);
+}
+
+AnimationClipVector Animator::GetAnimationClips()const
+{
+ return m_Controller->GetAnimationClips();
+}
+
+UnityEngine::Animation::AnimationSetBindings* Animator::GetAnimationSetBindings()const
+{
+ return m_Controller->GetAnimationSetBindings();
+}
+
+bool Animator::GetAnimationClipState (int layerIndex, bool currentState, dynamic_array<AnimationInfo>& output)
+{
+ if (!ValidateLayerIndex(layerIndex))
+ return false;
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+
+ mecanim::uint32_t motionSetIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineMotionSetIndex;
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+
+ float blendFactor = currentState ? 1 - m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_StateMachineOutput[stateMachineIndex]->m_BlendFactor : m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_StateMachineOutput[stateMachineIndex]->m_BlendFactor;
+
+ mecanim::statemachine::BlendNodeLayer *blendNodeLayer = currentState ? &m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_StateMachineOutput[stateMachineIndex]->m_Left.m_BlendNodeLayer[motionSetIndex] :
+ apStateMachineMem->m_InTransition ? &m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_StateMachineOutput[stateMachineIndex]->m_Right.m_BlendNodeLayer[motionSetIndex] : 0;
+
+ int clipCount = blendNodeLayer ? blendNodeLayer->m_OutputCount : 0 ;
+
+ AnimationClipVector const allClips = GetAnimationClips();
+
+ int layerClipOffset = GetLayerClipOffset(layerIndex);
+
+ output.resize_uninitialized(clipCount);
+ for(int i = 0 ; i < clipCount ; i++)
+ {
+ int index = blendNodeLayer->m_OutputIndexArray[i];
+
+ AnimationInfo& animInfo = output[i];
+ animInfo.clip = allClips[index+layerClipOffset];
+ animInfo.weight = blendNodeLayer->m_OutputBlendArray[i] * blendFactor;
+ }
+
+ return true;
+}
+
+
+bool Animator::GetAnimatorStateInfo (int layerIndex, bool currentState, AnimatorStateInfo& output)
+{
+ if (!ValidateLayerIndex(layerIndex))
+ return false;
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+ const mecanim::statemachine::StateMachineConstant* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+
+ mecanim::uint32_t stateIndex = currentState ? apStateMachineMem->m_CurrentStateIndex : apStateMachineMem->m_InTransition ? apStateMachineMem->m_NextStateIndex : apStateMachineMem->m_StateMemoryCount;
+ if (stateIndex < apStateMachineMem->m_StateMemoryCount)
+ {
+ output.nameHash = apStateMachineConst->m_StateConstantArray[stateIndex]->m_NameID;
+ output.pathHash = apStateMachineConst->m_StateConstantArray[stateIndex]->m_PathID;
+ output.normalizedTime = apStateMachineMem->m_StateMemoryArray[stateIndex]->m_PreviousTime;
+ output.length = apStateMachineMem->m_StateMemoryArray[stateIndex]->m_Duration;
+ output.tagHash = apStateMachineConst->m_StateConstantArray[stateIndex]->m_TagID;
+ output.loop = apStateMachineConst->m_StateConstantArray[stateIndex]->m_Loop ? 1 : 0;
+
+ return true;
+
+ }
+ else
+ return false;
+}
+
+
+bool Animator::GetAnimatorTransitionInfo(int layerIndex, AnimatorTransitionInfo& info)
+{
+ if(!ValidateLayerIndex(layerIndex))
+ return false;
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+ const mecanim::statemachine::StateMachineConstant* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+
+ if (apStateMachineMem->m_InTransition)
+ {
+ mecanim::statemachine::TransitionConstant const* transition = mecanim::statemachine::GetTransitionConstant(apStateMachineConst, apStateMachineConst->m_StateConstantArray[apStateMachineMem->m_CurrentStateIndex].Get(), apStateMachineMem->m_TransitionId);
+ if(transition)
+ {
+ info.nameHash = transition->m_ID;
+ info.userNameHash = transition->m_UserID;
+ }
+ // Dynamic transition doesn't exist, they are created on demand by user.
+ else
+ {
+ info.nameHash = 0;
+ info.userNameHash = 0;
+ }
+ info.normalizedTime = apStateMachineMem->m_TransitionTime;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+string Animator::GetAnimatorStateName (int layerIndex, bool currentState)
+{
+ if (!ValidateLayerIndex(layerIndex))
+ return "";
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+ const mecanim::statemachine::StateMachineConstant* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+ mecanim::statemachine::StateMachineMemory const* apStateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+
+ mecanim::uint32_t stateIndex = currentState ? apStateMachineMem->m_CurrentStateIndex : apStateMachineMem->m_InTransition ? apStateMachineMem->m_NextStateIndex : apStateMachineMem->m_StateMemoryCount;
+ if (stateIndex < apStateMachineMem->m_StateMemoryCount)
+ {
+ return m_Controller->StringFromID(apStateMachineConst->m_StateConstantArray[stateIndex]->m_PathID);
+ }
+
+ return "";
+}
+
+void Animator::GetRootBlendTreeConstantAndWorkspace (int layerIndex, int stateHash, mecanim::animation::BlendTreeNodeConstant const* & constant, mecanim::animation::BlendTreeWorkspace*& workspace)
+{
+ if(!ValidateLayerIndex(layerIndex))
+ return;
+
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineIndex;
+ mecanim::uint32_t motionSetIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[layerIndex]->m_StateMachineMotionSetIndex;
+
+ mecanim::statemachine::StateMachineConstant const* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+ mecanim::statemachine::StateMachineWorkspace* apStateMachineWorkspace = m_EvaluationDataSet.m_AvatarWorkspace->m_ControllerWorkspace->m_StateMachineWorkspace[stateMachineIndex];
+
+ for (int i=0; i<apStateMachineConst->m_StateConstantCount; i++)
+ {
+ if (CompareStateID (apStateMachineConst->m_StateConstantArray[i].Get(), stateHash))
+ {
+ constant = apStateMachineConst->m_StateConstantArray[i]->m_BlendTreeConstantArray[motionSetIndex]->m_NodeArray[0].Get();
+ workspace = apStateMachineWorkspace->m_StateWorkspaceArray[i]->m_BlendTreeWorkspaceArray[motionSetIndex];
+ }
+ }
+}
+
+float Animator::GetFeetPivotActive()
+{
+ if(!IsAvatarInitialize())
+ return false;
+
+ return m_EvaluationDataSet.m_AvatarInput->m_FeetPivotActive;
+}
+void Animator::SetFeetPivotActive(float value)
+{
+ if(!IsAvatarInitialize())
+ return ;
+
+ m_EvaluationDataSet.m_AvatarInput->m_FeetPivotActive = value;
+}
+
+bool Animator::GetStabilizeFeet()
+{
+ if(!IsAvatarInitialize())
+ return false;
+
+ return m_EvaluationDataSet.m_AvatarInput->m_StabilizeFeet;
+
+}
+void Animator::SetStabilizeFeet(bool value)
+{
+ if(!IsAvatarInitialize())
+ return ;
+
+ m_EvaluationDataSet.m_AvatarInput->m_StabilizeFeet = value;
+}
+
+float Animator::GetLeftFeetBottomHeight()
+{
+ if(!m_Avatar.IsValid())
+ return 0.f;
+
+ return m_Avatar->GetLeftFeetBottomHeight();
+}
+
+float Animator::GetRightFeetBottomHeight()
+{
+ if(!m_Avatar.IsValid())
+ return 0.f;
+
+ return m_Avatar->GetRightFeetBottomHeight();
+}
+
+#if UNITY_EDITOR
+
+void Animator::WriteSkeletonPose(mecanim::skeleton::SkeletonPose& pose)
+{
+ PROFILER_AUTO(gAnimatorWriteSkeletonPose, this)
+
+ if (m_EvaluationDataSet.m_GenericBindingConstant != NULL)
+ {
+ SetHumanTransformPropertyValues (*m_EvaluationDataSet.m_AvatarBindingConstant, pose);
+ SetTransformPropertyApplyMainThread (GetComponent(Transform), *m_EvaluationDataSet.m_GenericBindingConstant, *m_EvaluationDataSet.m_AvatarBindingConstant,false); // we don't skip root for ui update pose stuff
+ }
+ else
+ {
+ ErrorString("Failed to write skeleton pose");
+ }
+}
+
+void Animator::WriteHumanPose(mecanim::human::HumanPose &pose)
+{
+ // this function is only called from the editor.
+ // always fetch the avatar constant from the Avatar asset in case it been updated.
+ // Looking if the pptr is valid
+ if(!m_Avatar.IsValid())
+ return;
+
+ // Looking if the mecanim avatar constant is valid
+ if(!m_Avatar->IsValid())
+ return;
+
+ if(!Prepare())
+ return;
+
+ mecanim::animation::AvatarConstant *avatar = m_Avatar->GetAsset();
+
+ if(avatar && avatar->isHuman())
+ {
+ mecanim::human::Human *human = avatar->m_Human.Get();
+ mecanim::human::RetargetTo(human,&pose,0,math::xformIdentity(),m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput,m_EvaluationDataSet.m_AvatarWorkspace->m_BodySkeletonPoseWs,m_EvaluationDataSet.m_AvatarWorkspace->m_BodySkeletonPoseWsA);
+ mecanim::animation::EvaluateAvatarEnd(avatar,m_EvaluationDataSet.m_AvatarInput,m_EvaluationDataSet.m_AvatarOutput,m_EvaluationDataSet.m_AvatarMemory,m_EvaluationDataSet.m_AvatarWorkspace,m_EvaluationDataSet.m_ControllerConstant);
+
+ WriteSkeletonPose(*m_EvaluationDataSet.m_AvatarOutput->m_SkeletonPoseOutput);
+ }
+ else
+ {
+ ErrorString("Failed to write human pose");
+ }
+}
+
+void Animator::WriteDefaultPose()
+{
+ // Looking if the pptr is valid
+ if(!m_Avatar.IsValid())
+ return;
+
+ // Looking if the mecanim avatar constant is valid
+ if(!m_Avatar->IsValid())
+ return;
+
+ if(!Prepare())
+ return;
+
+ mecanim::animation::AvatarConstant *avatar = m_Avatar->GetAsset();
+
+ WriteSkeletonPose(*avatar->m_AvatarSkeletonPose);
+}
+#endif
+
+bool Animator::IsAutoPlayingBack()
+{
+ return m_RecorderMode == eRecord && GetSpeed() < 0;
+}
+
+bool Animator::IsPlayingBack()
+{
+ return m_RecorderMode == ePlayback || IsAutoPlayingBack();
+}
+
+int Animator::GetLayerClipOffset(int layerIndex)
+{
+ int layerClipOffset = 0;
+ for(int i = 0; i < layerIndex ; i++)
+ {
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[i]->m_StateMachineIndex;
+ mecanim::uint32_t motionSetIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[i]->m_StateMachineMotionSetIndex;
+
+ if(stateMachineIndex != mecanim::DISABLED_SYNCED_LAYER_IN_NON_PRO)
+ {
+ const mecanim::statemachine::StateMachineConstant* apStateMachineConst = m_EvaluationDataSet.m_ControllerConstant->m_StateMachineArray[stateMachineIndex].Get();
+ int j;
+ for( j = 0 ; j < apStateMachineConst->m_StateConstantCount; j++)
+ {
+ layerClipOffset += apStateMachineConst->m_StateConstantArray[j]->m_LeafInfoArray[motionSetIndex].m_Count ;
+ }
+ }
+ }
+
+ return layerClipOffset;
+}
+
+bool Animator::ShouldInterruptMatchTarget()const
+{
+ if(IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+ return false;
+
+ // case 516805: Match target must be interrupted if we begin a transition.
+ if(IsInTransitionInternal(0))
+ return true;
+
+ // case 542102: when the frame rate drop we miss the end of the match target and the last match target parameter stick.
+ // detect such case in TargetMatch by watching the initial state id
+ if(!IsMatchingTarget())
+ return true;
+
+ return false;
+}
+
+void Animator::TargetMatch(bool matchCurrentFrame)
+{
+ if(ShouldInterruptMatchTarget())
+ InterruptMatchTarget(false);
+
+ if(IsInitialize() && IsMatchingTarget())
+ {
+ math::xform avatarX = m_EvaluationDataSet.m_AvatarMemory->m_AvatarX;
+
+ math::float1 scale(m_EvaluationDataSet.m_AvatarConstant->m_Human->m_Scale);
+ math::xform dx = m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX;
+ dx.t *= scale;
+ avatarX = math::xformMul(avatarX, dx);
+
+ float stateTime = 0 ;
+ float stateDuration = 1 ;
+ if( m_EvaluationDataSet.m_ControllerConstant->m_LayerCount > 0)
+ {
+ mecanim::uint32_t stateMachineIndex = m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[0]->m_StateMachineIndex;
+ mecanim::statemachine::StateMachineMemory const* stateMachineMem = m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_StateMachineMemory[stateMachineIndex].Get();
+ if(stateMachineMem->m_StateMemoryCount > 0 )
+ {
+ stateTime = stateMachineMem->m_StateMemoryArray[stateMachineMem->m_CurrentStateIndex]->m_PreviousTime;
+ stateDuration = stateMachineMem->m_StateMemoryArray[stateMachineMem->m_CurrentStateIndex]->m_Duration;
+ }
+ }
+
+ if(stateTime >= m_MatchStartTime)
+ {
+ float endTime = m_EvaluationDataSet.m_AvatarInput->m_TargetTime;
+
+ float normalizeDeltaTime = m_EvaluationDataSet.m_AvatarInput->m_DeltaTime / stateDuration;
+ float remainingTime = max(0.0f, endTime - stateTime );
+ float w = remainingTime != 0 ? normalizeDeltaTime / remainingTime : 1;
+ w = math::saturate(w);
+
+ math::float4 targetT = m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_TargetX.t;
+ math::float4 targetQ = m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_TargetX.q;
+
+ mecanim::uint32_t targetIndex = m_EvaluationDataSet.m_AvatarInput->m_TargetIndex;
+
+ if(matchCurrentFrame)
+ {
+ w = 1;
+ if(targetIndex == mecanim::animation::kTargetReference)
+ {
+ targetT = math::float4::zero();
+ targetQ = math::quatIdentity();
+ }
+ else if(targetIndex == mecanim::animation::kTargetRoot)
+ {
+ targetT = m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.t;
+ targetQ = m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_RootX.q;
+ }
+ else if (targetIndex >= mecanim::animation::kTargetLeftFoot && targetIndex <= mecanim::animation::kTargetRightHand)
+ {
+ targetT = m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[targetIndex-2].m_X.t;
+ targetQ = m_EvaluationDataSet.m_AvatarOutput->m_HumanPoseOutput->m_GoalArray[targetIndex-2].m_X.q;
+ }
+ }
+
+
+ if (targetIndex >= mecanim::animation::kTargetLeftFoot && targetIndex <= mecanim::animation::kTargetRightHand)
+ targetQ = math::normalize(math::quatMul(targetQ,mecanim::human::HumanGetGoalOrientationOffset(mecanim::human::Goal(targetIndex - mecanim::animation::kTargetLeftFoot))));
+
+
+ // from muscleclips, local to avatarX
+ math::xform targetXLocal(targetT,targetQ, math::scaleIdentity());
+
+ // from user, where we need to be, in world space
+ math::xform matchX(Vector3fTofloat4(m_MatchPosition), QuaternionfTofloat4(m_MatchRotation), math::scaleIdentity());
+
+ // make match local to avatar X
+ math::xform matchXLocal = math::xformInvMul(avatarX, matchX); // @Sonny: any problem here for match target bug on IOS
+
+ // m_EvaluationDataSet.m_AvatarOutput->m_DX is in normalized space, so dx musy be computed in normalized space too.
+ math::xform dx;
+ dx.t = matchXLocal.t - targetXLocal.t; // dt not influenced by rotation
+ dx.t *= rcp(scale);
+ dx.q = quatMul(matchXLocal.q, quatConj(targetXLocal.q));
+ dx.t *= Vector3fTofloat4(w*m_MatchTargetMask.m_PositionXYZWeight);
+ dx.q = math::quatLerp( math::quatIdentity(), dx.q, math::float1(w*m_MatchTargetMask.m_RotationWeight));
+
+ m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX = math::xformMul(m_EvaluationDataSet.m_AvatarOutput->m_MotionOutput->m_DX,dx);
+
+ if(w >= 1)
+ {
+ InterruptMatchTarget(false);
+ }
+ }
+ }
+}
+
+bool Animator::ValidateGoalIndex(int index)
+{
+ if(index < mecanim::human::kLeftFootGoal || index > mecanim::human::kRightHandGoal )
+ {
+ ErrorString("Invalid Goal Index");
+ return false;
+ }
+
+ return true;
+}
+
+bool Animator::ValidateTargetIndex(int index)
+{
+ if(index < mecanim::animation::kTargetReference || index > mecanim::animation::kTargetRightHand )
+ {
+ ErrorString("Invalid Target Index");
+ return false;
+ }
+
+ return true;
+}
+
+bool Animator::ValidateLayerIndex(int index)const
+{
+ if(!IsInitialize() )
+ return false;
+
+ if(index < 0 || index >= GetLayerCount())
+ {
+ ErrorString("Invalid Layer Index");
+ return false;
+ }
+
+ if(m_EvaluationDataSet.m_ControllerConstant->m_LayerArray[index]->m_StateMachineIndex == mecanim::DISABLED_SYNCED_LAYER_IN_NON_PRO)
+ {
+ ErrorString("Sync Layer is only supported in Unity Pro");
+ return false;
+ }
+
+ return true;
+}
+
+void Animator::ValidateParameterID (GetSetValueResult result, int identifier)
+{
+ ValidateParameterString (result, Format("Hash %d", identifier));
+}
+
+void Animator::ValidateParameterString (GetSetValueResult result, const std::string& name)
+{
+
+ if (result == kParameterMismatchFailure)
+ {
+ WarningStringIfLoggingActive(Format ("Parameter type '%s' does not match.", name.c_str()));
+ }
+ else if (result == kParameterDoesNotExist)
+ {
+ WarningStringIfLoggingActive(Format ("Parameter '%s' does not exist.", name.c_str()));
+ }
+ else if (result == kAnimatorNotInitialized)
+ {
+ WarningStringIfLoggingActive("Animator has not been initialized.");
+ }
+ else if (result == kParameterIsControlledByCurve)
+ {
+ WarningStringIfLoggingActive(Format("Parameter '%s' is controlled by a curve.", name.c_str()));
+ }
+}
+
+template<typename T> inline bool IsTypeMatching(mecanim::ValueConstant const& valueConstant, T const& value)
+{
+ return valueConstant.m_Type == mecanim::traits<T>::type() ||
+ (valueConstant.m_Type == mecanim::kTriggerType && mecanim::traits<T>::type() == mecanim::kBoolType);
+}
+
+template<typename T> inline
+GetSetValueResult Animator::SetValue(mecanim::uint32_t id, T const& value)
+{
+ if(IsPlayingBack())
+ return kAnimatorInPlaybackMode;
+
+ if (!IsInitialize())
+ return kAnimatorNotInitialized;
+
+ mecanim::int32_t i = mecanim::FindValueIndex(m_EvaluationDataSet.m_ControllerConstant->m_Values.Get(), id);
+ if( i == -1)
+ return kParameterDoesNotExist;
+
+ bool isCurve = m_EvaluationDataSet.m_GenericBindingConstant->controllerBindingConstant->m_AnimationSet->m_AdditionalIndexArray[i] != -1;
+ if (isCurve)
+ return kParameterIsControlledByCurve;
+
+ if (!IsTypeMatching(m_EvaluationDataSet.m_ControllerConstant->m_Values->m_ValueArray[i], value))
+ return kParameterMismatchFailure;
+
+ m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_Values->WriteData(value, m_EvaluationDataSet.m_ControllerConstant->m_Values->m_ValueArray[i].m_Index);
+ return kGetSetSuccess;
+}
+
+template<typename T> inline
+GetSetValueResult Animator::GetValue(mecanim::uint32_t id, T& value)
+{
+ if (!IsInitialize())
+ {
+ value = T();
+ return kAnimatorNotInitialized;
+ }
+
+ mecanim::int32_t i = mecanim::FindValueIndex(m_EvaluationDataSet.m_ControllerConstant->m_Values.Get(), id);
+ if ( i == -1)
+ {
+ value = T();
+ return kParameterDoesNotExist;
+ }
+
+ if (! IsTypeMatching(m_EvaluationDataSet.m_ControllerConstant->m_Values->m_ValueArray[i], value) )
+ {
+ value = T();
+ return kParameterMismatchFailure;
+ }
+
+
+ m_EvaluationDataSet.m_AvatarMemory->m_ControllerMemory->m_Values->ReadData(value, m_EvaluationDataSet.m_ControllerConstant->m_Values->m_ValueArray[i].m_Index);
+
+ return kGetSetSuccess;
+}
+
+
+void Animator::WarningStringIfLoggingActive(const std::string& warning) const
+{
+ WarningStringIfLoggingActive(warning.c_str());
+}
+
+/// add or modify curve for a read only imported clip ... put Animation Window modification to curve in .meta
+
+void Animator::WarningStringIfLoggingActive(const char* warning) const
+{
+ if(m_LogWarnings)
+ {
+ WarningStringObject(warning ,this);
+ }
+}
+
+Transform* Animator::GetAvatarRoot()
+{
+ Transform *root = &GetComponent(Transform);
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_3_a1))
+ {
+ if(m_Avatar.IsValid())
+ {
+ Transform* effectiveRoot = 0;
+ if(m_Avatar->GetAsset()&& m_Avatar->GetAsset()->m_AvatarSkeleton.Get())
+ {
+ effectiveRoot = FindAvatarRoot(m_Avatar->GetAsset()->m_AvatarSkeleton.Get(), m_Avatar->GetAsset()->m_SkeletonNameIDArray.Get(), *root, m_HasTransformHierarchy) ;
+ }
+ if(effectiveRoot) root = effectiveRoot;
+ }
+ }
+
+ return root;
+}
+
+
+///@TODO:
+
+/// * How does a user setup the animation range in the animation window?
+/// * Figure out how we want to move loop blend feel intuitive.
+
+/// * LivePreview pretends that non-looping animations loop. At least that looks like what it is visualizing...
+/// * Blending of dynamic values does not treat default values correctly
+/// muscle clip info for animation window created clips
+/// visible avatar for some objects like lights
+/// * Figure out how we will handle startTime / stopTime from AnimationWindow.
+
+///@TODO: Write Test
+/// Create cube with light attached
+/// * Create Animation clip with transform changes -> Automatically applies root motion
+/// * Animated child object is animated as normal position movement
+/// * Blending between one clip that has a property and one that doesn't
diff --git a/Runtime/Animation/Animator.h b/Runtime/Animation/Animator.h
new file mode 100644
index 0000000..c481187
--- /dev/null
+++ b/Runtime/Animation/Animator.h
@@ -0,0 +1,537 @@
+#pragma once
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Animation/AvatarPlayback.h"
+#include "Runtime/Misc/UserList.h"
+#include "Runtime/mecanim/statemachine/statemachine.h"
+
+namespace math
+{
+ struct xform;
+}
+class Avatar;
+class Renderer;
+class Transform;
+class AnimationClip;
+class AnimatorController;
+class RuntimeAnimatorController;
+class AnimatorOverrideController;
+
+namespace mecanim
+{
+ namespace animation
+ {
+ struct AvatarConstant;
+ struct AvatarInput;
+ struct AvatarOutput;
+ struct AvatarMemory;
+ struct AvatarWorkspace;
+ struct ControllerConstant;
+ struct ControllerMemory;
+ struct AnimatorOverrideController;
+ struct AnimationSetMemory;
+ struct BlendTreeNodeConstant;
+ struct BlendTreeWorkspace;
+ }
+
+ namespace skeleton
+ {
+ struct SkeletonPose;
+ }
+ namespace human
+ {
+ struct HumanPose;
+ }
+}
+
+namespace UnityEngine
+{
+ namespace Animation
+ {
+ struct AvatarBindingConstant;
+ struct AnimatorGenericBindingConstant;
+ struct AnimatorTransformBindingConstant;
+ struct AnimationSetBindings;
+ }
+}
+
+
+
+enum GetSetValueResult { kGetSetSuccess = 0, kParameterMismatchFailure = 1, kParameterDoesNotExist = 2, kAnimatorNotInitialized = 3, kParameterIsControlledByCurve = 4, kAnimatorInPlaybackMode = 5 };
+
+struct AnimationInfo
+{
+ PPtr<AnimationClip> clip;
+ float weight;
+
+ AnimationInfo()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ weight = 0;
+ }
+};
+
+/// This struct must be kept in sync with the C# version in AnimatorBindings.txt
+struct AnimatorTransitionInfo
+{
+ int nameHash;
+ int userNameHash;
+ float normalizedTime;
+
+ AnimatorTransitionInfo()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ nameHash = 0;
+ userNameHash = 0;
+ normalizedTime = 0;
+ }
+};
+
+/// This struct must be kept in sync with the C# version in AnimatorBindings.txt
+struct AnimatorStateInfo
+{
+ int nameHash;
+ int pathHash;
+ float normalizedTime;
+ float length;
+ int tagHash;
+ int loop;
+
+ AnimatorStateInfo()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ pathHash = 0;
+ nameHash = 0;
+ normalizedTime = 0;
+ length = 0;
+ tagHash = 0;
+ loop = 0;
+ }
+};
+
+/// This struct must be kept in sync with the C# version in AnimatorBindings.txt
+struct MatchTargetWeightMask
+{
+ MatchTargetWeightMask(Vector3f positionXYZWeight, float rotationWeight)
+ : m_PositionXYZWeight(positionXYZWeight), m_RotationWeight(rotationWeight) {}
+
+ Vector3f m_PositionXYZWeight;
+ float m_RotationWeight;
+};
+
+enum RecorderMode
+{
+ eNormal = 0,
+ ePlayback= 1,
+ eRecord =2
+};
+
+typedef std::vector<PPtr<AnimationClip> > AnimationClipVector;
+
+class Animator: public Behaviour
+{
+public:
+
+ enum CullingMode { kCullAlwaysAnimate = 0, kCullBasedOnRenderers = 1 };
+
+ REGISTER_DERIVED_CLASS (Animator, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Animator)
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ Animator (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+ virtual void Deactivate (DeactivateOperation operation);
+ virtual void Reset ();
+ virtual void CheckConsistency();
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+ void Update (float deltaTime);
+ bool Sample (AnimationClip& clip, float inTime);
+
+ virtual void TransformChanged (int changeMask);
+ void OnAddComponent(Component* com);
+
+ bool IsValid() const;
+ bool IsInManagerList() const;
+
+ RuntimeAnimatorController* GetRuntimeAnimatorController() const;
+ void SetRuntimeAnimatorController(RuntimeAnimatorController* animation) ;
+
+ AnimatorController* GetAnimatorController() const;
+ AnimatorOverrideController* GetAnimatorOverrideController() const;
+
+ Avatar* GetAvatar();
+ void SetAvatar(Avatar* avatar);
+
+ const mecanim::animation::AvatarConstant* GetAvatarConstant();
+
+ bool IsOptimizable() const;
+ bool IsHuman() const;
+ bool HasRootMotion() const;
+ float GetHumanScale() const;
+
+ GetSetValueResult GetFloat(int id, float& value);
+ GetSetValueResult SetFloat(int id, float value);
+ GetSetValueResult SetFloatDamp(int id, float value, float dampTime, float deltaTime);
+
+ GetSetValueResult GetInteger(int id, int& output);
+ GetSetValueResult SetInteger(int id, int integer);
+
+ GetSetValueResult GetBool(int id, bool& output);
+ GetSetValueResult SetBool(int id, bool value);
+
+ GetSetValueResult ResetTrigger(int id);
+ GetSetValueResult SetTrigger(int id);
+
+ bool HasParameter(int id);
+
+ bool GetMuscleValue(int id, float *value);
+
+ GetSetValueResult ParameterControlledByCurve(int id);
+
+ Vector3f GetAvatarPosition();
+ Quaternionf GetAvatarRotation();
+ Vector3f GetAvatarScale();
+
+ void SetAvatarPosition(const Vector3f& rootPosition);
+ void SetAvatarRotation(const Quaternionf& rootRotation);
+ void SetAvatarScale(const Vector3f&rootScale);
+
+ Vector3f GetDeltaPosition();
+ Quaternionf GetDeltaRotation();
+
+ Vector3f GetBodyPosition();
+ Quaternionf GetBodyRotation();
+
+ void SetBodyPosition(const Vector3f& rootPosition);
+ void SetBodyRotation(const Quaternionf& rootRotation);
+
+ float GetPivotWeight();
+ Vector3f GetPivotPosition();
+
+ bool GetApplyRootMotion () const { return m_ApplyRootMotion; }
+ void SetApplyRootMotion (bool rootMotion);
+
+ bool GetAnimatePhysics () const { return m_AnimatePhysics; }
+ void SetAnimatePhysics (bool animatePhysics);
+
+ float GetGravityWeight();
+
+ bool SupportsOnAnimatorMove();
+
+ void MatchTarget(Vector3f const& matchPosition, Quaternionf const& matchRotation, int targetIndex, const MatchTargetWeightMask& mask, float startNormalizedTime, float targetNormalizedTime);
+ void InterruptMatchTarget(bool completeMatch = true);
+ bool IsMatchingTarget()const;
+
+ void SetSpeed(float speed);
+ float GetSpeed() const ;
+
+ void GotoState(int layer, int stateId, float normalizedTime, float transitionDuration, float transitionTime = 0.0F);
+
+ void SetTarget(int targetIndex, float targetNormalizedTime);
+ Vector3f GetTargetPosition();
+ Quaternionf GetTargetRotation();
+
+ bool IsBoneTransform(Transform *transform);
+ Transform* GetBoneTransform(int humanBoneId);
+
+ Vector3f GetGoalPosition(int index);
+ void SetGoalPosition(int index, Vector3f const& pos);
+
+ Quaternionf GetGoalRotation(int index);
+ void SetGoalRotation(int index, Quaternionf const& rot);
+
+ void SetGoalWeightPosition(int index, float value);
+ void SetGoalWeightRotation(int index, float value);
+
+ float GetGoalWeightPosition(int index);
+ float GetGoalWeightRotation(int index);
+
+ void SetLookAtPosition(Vector3f lookAtPosition);
+ void SetLookAtClampWeight(float weight);
+ void SetLookAtBodyWeight(float weight);
+ void SetLookAtHeadWeight(float weight);
+ void SetLookAtEyesWeight(float weight);
+
+ int GetLayerCount()const;
+ std::string GetLayerName(int layerIndex);
+ float GetLayerWeight(int layerIndex);
+ void SetLayerWeight(int layerIndex, float w);
+
+ void SetCullingMode (CullingMode mode);
+ CullingMode GetCullingMode () const { return m_CullingMode; }
+
+ bool IsInTransition(int layerIndex)const;
+
+ bool ShouldInterruptMatchTarget()const;
+
+ bool GetAnimatorStateInfo (int layerIndex, bool currentState, AnimatorStateInfo& output);
+ bool GetAnimatorTransitionInfo (int layerIndex, AnimatorTransitionInfo& output);
+
+ string GetAnimatorStateName (int layerIndex, bool currentState);
+
+ bool GetAnimationClipState(int layerIndex, bool currentState, dynamic_array<AnimationInfo>& output);
+
+ void GetRootBlendTreeConstantAndWorkspace (int layerIndex, int stateHash, mecanim::animation::BlendTreeNodeConstant const* & constant, mecanim::animation::BlendTreeWorkspace*& workspace);
+
+ float GetFeetPivotActive();
+ void SetFeetPivotActive(float value);
+
+ bool GetStabilizeFeet();
+ void SetStabilizeFeet(bool value);
+
+ float GetLeftFeetBottomHeight();
+ float GetRightFeetBottomHeight();
+
+ void WriteHumanPose(mecanim::human::HumanPose &pose);
+ void WriteDefaultPose();
+
+ const mecanim::skeleton::SkeletonPose* GetGlobalSpaceSkeletonPose () const;
+
+ void StartPlayback();
+ void SetPlaybackTime(float time);
+ float GetPlaybackTime();
+ void StopPlayback();
+
+ void PrepareForPlayback();
+ void StartRecording(int frameCount = 0);
+ void StopRecording();
+
+ float GetRecorderStartTime();
+ float GetRecorderStopTime();
+
+ int GetBehaviourIndex () { return m_BehaviourIndex; }
+ void SetBehaviourIndex (int index) { m_BehaviourIndex = index; }
+
+ int GetFixedBehaviourIndex () { return m_FixedBehaviourIndex; }
+ void SetFixedBehaviourIndex (int index) { m_FixedBehaviourIndex = index; }
+
+ static void UpdateAvatars (Animator** inputAvatars, size_t inputSize, float deltaTime, bool doFKMove, bool doRetargetIKWrite);
+
+ bool IsAvatarInitialize() const;
+ bool IsInitialize() const;
+
+ void ValidateParameterString (GetSetValueResult result, const std::string& parameterName);
+ void ValidateParameterID (GetSetValueResult result, int identifier);
+
+
+ void SetLayersAffectMassCenter(bool value);
+ bool GetLayersAffectMassCenter() const ;
+
+ void SetHasTransformHierarchy (bool value);
+ bool GetHasTransformHierarchy () const { return m_HasTransformHierarchy; }
+
+ void EvaluateSM();
+
+ GET_SET(bool, LogWarnings, m_LogWarnings);
+ GET_SET(bool, FireEvents, m_FireEvents);
+
+ std::string GetPerformanceHints();
+
+ AnimationClipVector GetAnimationClips()const;
+ UnityEngine::Animation::AnimationSetBindings* GetAnimationSetBindings()const;
+
+
+ Transform* GetAvatarRoot();
+protected:
+
+ struct MecanimDataSet
+ {
+ MecanimDataSet():
+ m_AvatarConstant(0),
+ m_AvatarInput(0),
+ m_AvatarOutput(0),
+ m_AvatarMemory(0),
+ m_AvatarWorkspace(0),
+ m_ControllerConstant(0),
+ m_ControllerMemory(0),
+ m_AnimationSetMemory(0),
+ m_GenericBindingConstant(0),
+ m_AvatarBindingConstant(0),
+ m_AvatarMemorySize(0),
+ m_OwnsAvatar(false)
+ {
+ }
+
+ mecanim::animation::AvatarConstant const* m_AvatarConstant;
+ mecanim::animation::AvatarInput* m_AvatarInput;
+ mecanim::animation::AvatarOutput* m_AvatarOutput;
+ mecanim::animation::AvatarMemory* m_AvatarMemory;
+ mecanim::animation::AvatarWorkspace* m_AvatarWorkspace;
+ mecanim::animation::ControllerConstant const* m_ControllerConstant;
+ mecanim::animation::ControllerMemory* m_ControllerMemory;
+ mecanim::animation::AnimationSetMemory* m_AnimationSetMemory;
+
+ UnityEngine::Animation::AnimatorGenericBindingConstant* m_GenericBindingConstant;
+ UnityEngine::Animation::AvatarBindingConstant* m_AvatarBindingConstant;
+
+ size_t m_AvatarMemorySize;
+
+ bool m_OwnsAvatar;
+
+ void Reset()
+ {
+ m_AvatarConstant=0;
+ m_AvatarInput=0;
+ m_AvatarOutput=0;
+ m_AvatarMemory=0;
+ m_AvatarWorkspace=0;
+ m_ControllerConstant=0;
+ m_ControllerMemory=0;
+ m_AnimationSetMemory=0;
+ m_GenericBindingConstant=0;
+ m_AvatarBindingConstant=0;
+ m_AvatarMemorySize=0;
+ m_OwnsAvatar = false;
+ }
+ };
+
+ // Used by Animator::Sample to auto unregister bindings.
+ struct AutoMecanimDataSet
+ {
+ mecanim::memory::ChainedAllocator m_Alloc;
+ MecanimDataSet m_MecanimDataSet;
+
+ AutoMecanimDataSet(size_t size):m_Alloc(size){}
+ ~AutoMecanimDataSet();
+
+ Animator::MecanimDataSet const* operator ->() const{ return &m_MecanimDataSet; }
+ Animator::MecanimDataSet * operator ->(){ return &m_MecanimDataSet; }
+ Animator::MecanimDataSet const& operator *()const{ return m_MecanimDataSet; }
+ Animator::MecanimDataSet& operator *(){ return m_MecanimDataSet; }
+
+ void Reset();
+ };
+
+
+ void ClearObject();
+ void CreateObject();
+
+ void SetupAvatarMecanimDataSet(mecanim::animation::AvatarConstant const* avatarConstant, mecanim::memory::Allocator& allocator, Animator::MecanimDataSet& outMecanimDataSet);
+ void SetupControllerMecanimDataSet(mecanim::animation::ControllerConstant const* controllerConstant, UnityEngine::Animation::AnimationSetBindings const* animationSetBindings, mecanim::memory::Allocator& allocator, Animator::MecanimDataSet& outMecanimDataSet);
+
+ void WriteSkeletonPose(mecanim::skeleton::SkeletonPose& pose);
+
+ bool IsInTransitionInternal(int layerIndex)const;
+
+ bool Prepare();
+ void InitStep(float deltaTime);
+ void FKStep();
+ void RetargetStep();
+ void AvatarIKAndEndStep();
+ void AvatarWriteStep();
+ void ApplyOnAnimatorIK(int layerIndex);
+ void ApplyOnAnimatorMove();
+ void ApplyBuiltinRootMotion();
+ void FireAnimationEvents();
+
+ void Record(float deltaTime);
+
+
+ // Visibility culling
+ void ClearContainedRenderers ();
+ void RecomputeContainedRenderersRecurse (Transform& transform);
+ void InitializeVisibilityCulling ();
+ void SetVisibleRenderers(bool visible);
+ bool IsAnyRendererVisible () const;
+ void RemoveContainedRenderer (void* renderer);
+
+ static void AnimatorVisibilityCallback (void* userData, void* sender, int visibilityEvent);
+
+ static void* FKStepStatic (void* userData);
+ static void* RetargetStepStatic (void* userData);
+ static void* AvatarIKAndEndStepStatic (void* userData);
+ static void* AvatarWriteStepStatic (void* userData);
+
+ int m_BehaviourIndex;
+ int m_FixedBehaviourIndex;
+ bool m_Visible;
+ CullingMode m_CullingMode; ///< enum { Always Animate = 0, Based On Renderers = 1 }
+ PPtr<Avatar> m_Avatar;
+ PPtr<RuntimeAnimatorController> m_Controller;
+
+ mecanim::memory::MecanimAllocator mAlloc;
+
+ MecanimDataSet m_EvaluationDataSet;
+
+ AutoMecanimDataSet m_SamplingDataSet;
+
+ Vector3f m_DeltaPosition;
+ Quaternionf m_DeltaRotation;
+
+ Vector3f m_PivotPosition;
+
+ Vector3f m_TargetPosition;
+ Quaternionf m_TargetRotation;
+
+ float m_MatchStartTime;
+ int m_MatchStateID;
+ Vector3f m_MatchPosition;
+ Quaternionf m_MatchRotation;
+ MatchTargetWeightMask m_MatchTargetMask;
+ bool m_MustCompleteMatch;
+
+ bool m_ApplyRootMotion;
+ bool m_AnimatePhysics;
+
+ float m_Speed;
+
+ bool m_LogWarnings;
+ bool m_FireEvents;
+
+
+ typedef dynamic_array<Renderer*> ContainedRenderers;
+ ContainedRenderers m_ContainedRenderers;
+
+ UserListNode m_AnimatorAvatarNode;
+ UserListNode m_AnimatorControllerNode;
+
+ AvatarPlayback m_AvatarPlayback;
+ RecorderMode m_RecorderMode;
+ float m_PlaybackDeltaTime;
+ float m_PlaybackTime; // for query back
+ bool IsPlayingBack();
+ bool IsAutoPlayingBack();
+
+ bool m_HasTransformHierarchy;
+
+ void SetPlaybackTimeInternal(float time);
+
+ void UpdateInManager();
+
+ int GetLayerClipOffset(int layerIndex);
+
+ void TargetMatch(bool matchCurrentFrame = false);
+
+ bool ValidateGoalIndex(int index);
+ bool ValidateTargetIndex(int index);
+ bool ValidateLayerIndex(int index)const;
+ bool ValidateSubLayerIndex(int index, int subLayerIndex);
+
+ template<typename T>
+ GetSetValueResult SetValue(mecanim::uint32_t id, T const& value);
+
+ template<typename T>
+ GetSetValueResult GetValue(mecanim::uint32_t id, T& value);
+
+ void WarningStringIfLoggingActive(const char* warning) const;
+ void WarningStringIfLoggingActive(const std::string& warning) const;
+};
+
diff --git a/Runtime/Animation/AnimatorController.cpp b/Runtime/Animation/AnimatorController.cpp
new file mode 100644
index 0000000..d633364
--- /dev/null
+++ b/Runtime/Animation/AnimatorController.cpp
@@ -0,0 +1,693 @@
+
+#include "UnityPrefix.h"
+
+#include "AnimatorController.h"
+
+#include "Runtime/Animation/RuntimeAnimatorController.h"
+
+#include "Runtime/mecanim/animation/avatar.h"
+
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/mecanim/generic/stringtable.h"
+#include "Runtime/Animation/AnimationClip.h"
+#include "Runtime/Animation/AnimationSetBinding.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/Animation/StateMachine.h"
+#include "Editor/Src/Animation/AvatarMask.h"
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Editor/Src/AssetPipeline/AssetImporter.h"
+#endif
+
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+
+
+#define DIRTY_AND_INVALIDATE() OnInvalidateAnimatorController(); SetDirty();
+
+
+
+IMPLEMENT_OBJECT_SERIALIZE (AnimatorController)
+IMPLEMENT_CLASS_HAS_INIT(AnimatorController)
+
+
+
+AnimatorController::AnimatorController(MemLabelId label, ObjectCreationMode mode)
+ : Super(label, mode),
+ m_Allocator(1024*4),
+ m_Controller(0),
+ m_ControllerSize(0),
+ m_AnimationSetBindings(0),
+ m_IsAssetBundled(true)
+#if UNITY_EDITOR
+ ,
+ m_Dependencies(this)
+#endif
+{
+
+}
+
+AnimatorController::~AnimatorController()
+{
+#if UNITY_EDITOR
+ m_Dependencies.Clear();
+#endif
+
+ NotifyObjectUsers( kDidModifyAnimatorController );
+}
+
+void AnimatorController::InitializeClass ()
+{
+ REGISTER_MESSAGE_VOID(AnimatorController, kDidModifyMotion, OnInvalidateAnimatorController);
+
+#if UNITY_EDITOR
+ RegisterAllowNameConversion (AnimatorController::GetClassStringStatic(), "m_Layers", "m_AnimatorLayers");
+ RegisterAllowNameConversion (AnimatorController::GetClassStringStatic(), "m_AnimatorEvents", "m_AnimatorParameters");
+ RegisterAllowTypeNameConversion( "AnimatorEvent", "AnimatorControllerParameter") ;
+#endif
+}
+
+void AnimatorController::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+
+#if UNITY_EDITOR
+ OnInvalidateAnimatorController();
+
+ // Force load the AnimatorController
+ // This ensures that when we build a player the Controller is fully initialized.
+ // @TODO: This is kind of a hack. It would be better if we make sure when building a player we will ensure that the ControllerConstant has been created.
+ GetAsset();
+
+#endif
+
+ if (m_AnimationSetBindings == NULL && m_Controller != NULL)
+ {
+ RegisterAnimationClips();
+ m_AnimationSetBindings = UnityEngine::Animation::CreateAnimationSetBindings(m_Controller, GetAnimationClips(), m_Allocator);
+ }
+}
+
+
+void AnimatorController::CheckConsistency ()
+{
+ Super::CheckConsistency();
+#if UNITY_EDITOR
+
+ AnimatorControllerParameterVector toRemove;
+ for(int i=0;i<m_AnimatorParameters.size();++i)
+ {
+ // This is the old Vector type which is not supported anymore
+ if(m_AnimatorParameters[i].GetType() == 0)
+ toRemove.push_back(m_AnimatorParameters[i]);
+ }
+
+ for(int i=0;i<toRemove.size();++i)
+ {
+ for(int j=0;j<m_AnimatorParameters.size();++j)
+ {
+ if( strcmp(m_AnimatorParameters[j].GetName(), toRemove[i].GetName()) == 0 )
+ {
+ RemoveParameter(i);
+ break;
+ }
+ }
+ }
+
+ for(int i=0;i<m_AnimatorLayers.size();++i)
+ {
+ m_AnimatorLayers[i].SetController(this);
+
+ if( m_AnimatorLayers[i].GetSyncedLayerIndex() >= static_cast<int>(m_AnimatorLayers.size()))
+ {
+ m_AnimatorLayers[i].SetSyncedLayerIndexInternal(-1);
+ m_AnimatorLayers[i].SetStateMachineMotionSetIndex(0);
+ }
+
+ if( m_AnimatorLayers[i].GetSyncedLayerIndex() != -1)
+ {
+ StateMachine* stateMachine = m_AnimatorLayers[i].GetStateMachine();
+ int motionSetCount = stateMachine->GetMotionSetCount();
+
+ if(m_AnimatorLayers[i].GetStateMachineMotionSetIndex() >= motionSetCount)
+ {
+ // if there is only 2 motion set and only one layer is synchronize we can reconnect
+ if(motionSetCount == 2)
+ {
+ bool valid = true;
+ for(int j=0;j<m_AnimatorLayers.size() && valid;++j)
+ {
+ if( i!=j && m_AnimatorLayers[i].GetSyncedLayerIndex() == m_AnimatorLayers[j].GetSyncedLayerIndex())
+ valid = false;
+ }
+
+ if(valid)
+ m_AnimatorLayers[i].SetStateMachineMotionSetIndex(1);
+ else
+ {
+ m_AnimatorLayers[i].SetSyncedLayerIndexInternal(-1);
+ m_AnimatorLayers[i].SetStateMachineMotionSetIndex(0);
+ }
+ }
+ else
+ {
+ m_AnimatorLayers[i].SetSyncedLayerIndexInternal(-1);
+ m_AnimatorLayers[i].SetStateMachineMotionSetIndex(0);
+ }
+ }
+ }
+ }
+ for(int i=0;i<m_AnimatorParameters.size();++i)
+ {
+ m_AnimatorParameters[i].SetController(this);
+ }
+#endif
+}
+
+template <typename TransferFunction>
+bool IsLoadingFromAssetBundle (TransferFunction& transfer)
+{
+ return transfer.IsReading () && transfer.IsSerializingForGameRelease();
+}
+
+
+template<class TransferFunction>
+void AnimatorController::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.SetVersion(2);
+
+ if (transfer.IsSerializingForGameRelease())
+ {
+ TRANSFER(m_ControllerSize);
+
+ if(m_Controller == 0)
+ m_Allocator.Reserve(m_ControllerSize);
+
+ transfer.SetUserData(&m_Allocator);
+ TRANSFER_NULLABLE(m_Controller, mecanim::animation::ControllerConstant);
+ TRANSFER(m_TOS);
+
+ transfer.Transfer (m_AnimationClips, "m_AnimationClips");
+ }
+
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_AnimatorParameters);
+ transfer.Align();
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_AnimatorLayers);
+
+ // case 491674 Crash when avatar is selected in the Hiererchy when in play mode
+ // cannot display a controller in UI if it come from an asset bundle
+#if UNITY_EDITOR
+ if(transfer.IsReading ())
+ m_IsAssetBundled = IsLoadingFromAssetBundle(transfer) && m_AnimatorLayers.size() == 0;
+
+ if (transfer.IsRemapPPtrTransfer() && !transfer.IsSerializingForGameRelease())
+ {
+ transfer.Transfer (m_AnimationClips, "m_AnimationClips");
+ }
+#endif
+
+}
+
+
+#if UNITY_EDITOR
+
+void AnimatorController::BuildAsset()
+{
+ ClearAsset();
+
+ m_Dependencies.Clear();
+ m_TOS.clear();
+
+ // Insert all reserve keyword
+ mecanim::ReserveKeyword* staticTable = mecanim::ReserveKeywordTable();
+ int i;
+ for(i=0;i<mecanim::eLastString;i++)
+ {
+ m_TOS.insert( std::make_pair(staticTable[i].m_ID, std::string(staticTable[i].m_Keyword) ) );
+ }
+
+ /// Parameters
+ dynamic_array<mecanim::uint32_t> types (kMemTempAlloc);
+ dynamic_array<int> eventIds (kMemTempAlloc);
+ for(i = 0 ; i < GetParameterCount() ; i++)
+ {
+ AnimatorControllerParameter* parameter = GetParameter(i);
+ eventIds.push_back( mecanim::processCRC32( mecanim::String(parameter->GetName())));
+ m_TOS.insert( std::make_pair(eventIds[i], parameter->GetName()) );
+ types.push_back(parameter->GetType());
+ }
+
+ mecanim::ValueArrayConstant* values = mecanim::CreateValueArrayConstant(types.begin(), types.size(), m_Allocator);
+
+ for(i = 0 ; i < GetParameterCount() ; i++)
+ values->m_ValueArray[i].m_ID = eventIds[i];
+
+
+ mecanim::ValueArray* defaultValues = mecanim::CreateValueArray(values, m_Allocator);
+
+ for(i = 0 ; i < GetParameterCount() ; i++)
+ {
+ AnimatorControllerParameter* parameter = GetParameter(i);
+ switch(parameter->GetType())
+ {
+ case AnimatorControllerParameterTypeFloat:
+ {
+ float val = parameter->GetDefaultFloat();
+ defaultValues->WriteData(val, values->m_ValueArray[i].m_Index);
+ break;
+ }
+ case AnimatorControllerParameterTypeInt:
+ {
+ mecanim::int32_t val = parameter->GetDefaultInt();
+ defaultValues->WriteData(val, values->m_ValueArray[i].m_Index);
+ break;
+ }
+ case AnimatorControllerParameterTypeTrigger:
+ case AnimatorControllerParameterTypeBool:
+ {
+ bool val = parameter->GetDefaultBool();
+ defaultValues->WriteData(val, values->m_ValueArray[i].m_Index);
+ break;
+ }
+ }
+ }
+
+
+ /// Layers
+
+ std::vector<mecanim::animation::LayerConstant*> layerVector;
+ std::vector<mecanim::statemachine::StateMachineConstant*> stateMachineVector;
+ std::vector<int> stateMachineIndexVector;
+
+ int stateMachineIndex = 0;
+ for(int i = 0 ; i < GetLayerCount() ; i++)
+ {
+ if(m_AnimatorLayers[i].GetSyncedLayerIndex() == -1)
+ stateMachineIndexVector.push_back(stateMachineIndex++);
+ else
+ stateMachineIndexVector.push_back(-1);
+ }
+
+ for(int i = 0 ; i < GetLayerCount() ; i++)
+ {
+ int stateMachineIndex = 0;
+
+ StateMachine* editorStateMachine = m_AnimatorLayers[i].GetStateMachine();
+
+ if(m_AnimatorLayers[i].GetSyncedLayerIndex() == -1)
+ {
+ if(editorStateMachine)
+ {
+ mecanim::statemachine::StateMachineConstant* stateMachine = editorStateMachine->BuildRuntimeAsset(m_Dependencies, m_TOS, i, m_Allocator);
+ AssertIf(stateMachine == 0);
+ if(stateMachine)
+ {
+ editorStateMachine->GetAnimationClips(m_AnimationClips, m_AnimatorLayers[i].GetStateMachineMotionSetIndex());
+ }
+
+ stateMachineVector.push_back(stateMachine);
+ stateMachineIndex = stateMachineIndexVector[i];
+ }
+ }
+ else
+ {
+ if (!GetBuildSettings().hasAdvancedVersion)
+ {
+ ErrorString("Sync Layer is only supported in Unity Pro. Layer will be discarded in game");
+ stateMachineIndex = mecanim::DISABLED_SYNCED_LAYER_IN_NON_PRO;
+ }
+
+ else
+ {
+ stateMachineIndex = stateMachineIndexVector[m_AnimatorLayers[i].GetSyncedLayerIndex()];
+ if(editorStateMachine)
+ {
+ editorStateMachine->GetAnimationClips(m_AnimationClips, m_AnimatorLayers[i].GetStateMachineMotionSetIndex());
+ }
+ }
+ }
+
+ AnimatorControllerLayer &animatorLayer = m_AnimatorLayers[i];
+
+ mecanim::animation::LayerConstant* layer = mecanim::animation::CreateLayerConstant(stateMachineIndex, animatorLayer.GetStateMachineMotionSetIndex(), m_Allocator);
+ layer->m_Binding = mecanim::processCRC32( mecanim::String( animatorLayer.GetName()));
+ m_TOS.insert( std::make_pair(layer->m_Binding, animatorLayer.GetName()) );
+ layer->m_IKPass = animatorLayer.GetIKPass();
+ layer->m_LayerBlendingMode = animatorLayer.GetBlendingMode();
+ layer->m_DefaultWeight = animatorLayer.GetDefaultWeight();
+ layer->m_SyncedLayerAffectsTiming = animatorLayer.GetBlendingMode() == AnimatorLayerBlendingModeOverride ? animatorLayer.GetSyncedLayerAffectsTiming() : false;
+
+ AvatarMask* mask = animatorLayer.GetMask();
+ layer->m_BodyMask = mask != NULL ? mask->GetHumanPoseMask(m_Dependencies) : mecanim::human::FullBodyMask();
+ layer->m_SkeletonMask = mask != NULL ? mask->GetSkeletonMask(m_Dependencies, m_Allocator) : 0;
+
+ layerVector.push_back(layer);
+
+ }
+
+ // Early exit if no layer or statemachine
+ if(layerVector.size() == 0 || stateMachineVector.size() == 0)
+ return;
+
+
+ mecanim::animation::ControllerConstant* controllerConstant =
+ mecanim::animation::CreateControllerConstant( layerVector.size(), layerVector.size() ? &layerVector.front() : 0,
+ stateMachineVector.size(), stateMachineVector.size() ? &stateMachineVector.front() :0,
+ values, defaultValues, m_Allocator);
+ AssertIf(controllerConstant == 0);
+ if(controllerConstant)
+ {
+ m_Controller = controllerConstant;
+
+ BlobWrite::container_type data;
+ BlobWrite blobWrite (data, kNoTransferInstructionFlags, kBuildNoTargetPlatform);
+ blobWrite.Transfer(*m_Controller, "Base");
+
+ m_ControllerSize = data.size();
+
+ RegisterAnimationClips();
+ m_AnimationSetBindings = UnityEngine::Animation::CreateAnimationSetBindings(m_Controller, GetAnimationClips(), m_Allocator);
+ }
+
+}
+
+
+AnimatorControllerLayer* AnimatorController::GetLayer(int index)
+{
+ if(ValidateLayerIndex(index))
+ return &m_AnimatorLayers[index];
+
+ return 0;
+}
+
+const AnimatorControllerLayer* AnimatorController::GetLayer(int index) const
+{
+ if(ValidateLayerIndex(index))
+ return &m_AnimatorLayers[index];
+
+ return 0;
+}
+
+int AnimatorController::GetLayerCount() const
+{
+ return m_AnimatorLayers.size();
+}
+
+void AnimatorController::AddLayer(const std::string& name)
+{
+ m_IsAssetBundled = false;
+ AnimatorControllerLayer layer;
+ layer.SetController(this);
+
+ StateMachine *stateMachine = CreateObjectFromCode<StateMachine>();
+
+ stateMachine->SetHideFlags( this->TestHideFlag(kDontSave) ? kHideInHierarchy | kHideInspector | kDontSave : kHideInHierarchy | kHideInspector);
+ if(IsPersistent())
+ AddAssetToSameFile(*stateMachine, *this, true);
+
+ layer.SetStateMachine(stateMachine);
+ layer.SetName(name.c_str());
+
+ m_AnimatorLayers.push_back(layer);
+ DIRTY_AND_INVALIDATE();
+}
+
+void AnimatorController::RemoveLayer(int index)
+{
+ if(ValidateLayerIndex(index))
+ {
+ if(GetLayer(index)->GetSyncedLayerIndex() != -1)
+ GetLayer(index)->SetSyncedLayerIndex(-1); // this will remove motion set and ensure consistency
+
+ // Update sync layer index
+ for(int i=0;i<m_AnimatorLayers.size();++i)
+ {
+ AnimatorControllerLayer *layer = GetLayer(i);
+ if( i!=index && layer->GetSyncedLayerIndex() > index)
+ layer->SetSyncedLayerIndexInternal(layer->GetSyncedLayerIndex()-1);
+
+ // If a layer is sync on this layer break the synchronization
+ if( i!=index && layer->GetSyncedLayerIndex() == index)
+ layer->SetSyncedLayerIndex(-1);
+ }
+ m_AnimatorLayers.erase(m_AnimatorLayers.begin() + index);
+
+ DIRTY_AND_INVALIDATE();
+ }
+}
+
+
+int AnimatorController::GetParameterCount() const
+{
+ return m_AnimatorParameters.size();
+}
+
+
+void AnimatorController::AddParameter(const std::string& name, AnimatorControllerParameterType type)
+{
+ m_AnimatorParameters.push_back(AnimatorControllerParameter());
+ AnimatorControllerParameter *animatorParameter = GetParameter(GetParameterCount()-1);
+ animatorParameter->SetController(this);
+ animatorParameter->SetName(name.c_str());
+ animatorParameter->SetType(type);
+
+ int count = 0;
+ for(int i = 0 ; i < GetParameterCount(); i++)
+ if(GetParameter(i)->GetType() == type) count++;
+
+ if(count == 1)
+ {
+ for(int i = 0 ; i < GetLayerCount(); i++)
+ {
+ if(GetLayer(i)->GetStateMachine())
+ GetLayer(i)->GetStateMachine()->AddFirstParameterOfType(animatorParameter->GetName(),type);
+ }
+ }
+ DIRTY_AND_INVALIDATE();
+}
+
+void AnimatorController::RemoveParameter(int index)
+{
+ if(ValidateParameterIndex(index))
+ {
+ AnimatorControllerParameterType eventType = GetParameter(index)->GetType();
+
+ int otherSameTypeParameterIndex = -1;
+ //find other event of type
+ for(int i = 0 ; i < GetParameterCount() && otherSameTypeParameterIndex == -1; i++)
+ {
+ if( i != index && GetParameter(i)->GetType() == eventType)
+ otherSameTypeParameterIndex = i;
+ }
+
+ for(int i = 0 ; i < GetLayerCount(); i++)
+ {
+ if(GetLayer(i)->GetStateMachine())
+ GetLayer(i)->GetStateMachine()->RenameParameter(otherSameTypeParameterIndex != -1 ? GetParameter(otherSameTypeParameterIndex)->GetName() : "", GetParameter(index)->GetName());
+ }
+
+ m_AnimatorParameters.erase(m_AnimatorParameters.begin() + index);
+ DIRTY_AND_INVALIDATE();
+ }
+}
+
+int AnimatorController::FindParameter(const std::string&name) const
+{
+ int ret = -1;
+
+ for(int i = 0; i < m_AnimatorParameters.size() && ret == -1; i++)
+ {
+ if(strcmp (m_AnimatorParameters[i].GetName(), name.c_str()) == 0)
+ {
+ ret = i;
+ }
+ }
+
+ return ret;
+}
+
+AnimatorControllerParameter* AnimatorController::GetParameter(int index)
+{
+ if(ValidateParameterIndex(index))
+ return &m_AnimatorParameters[index];
+
+ return 0;
+}
+const AnimatorControllerParameter* AnimatorController::GetParameter(int index) const
+{
+ if(ValidateParameterIndex(index))
+ return &m_AnimatorParameters[index];
+
+ return 0;
+}
+
+
+std::vector<PPtr<Object> > AnimatorController::CollectObjectsUsingParameter(const string& parameterName)
+{
+ std::vector<PPtr<Object> > ret;
+ for(int i = 0 ; i < GetLayerCount() ; i++)
+ {
+ StateMachine* stateMachine = GetLayer(i)->GetStateMachine();
+
+ if(stateMachine)
+ {
+ std::vector<PPtr<Object> > currentRet = stateMachine->CollectObjectsUsingParameter(parameterName);
+ ret.insert(ret.end(), currentRet.begin(), currentRet.end());
+ }
+ }
+
+ return ret;
+}
+
+string AnimatorController::MakeUniqueParameterName(const string& newName) const
+{
+ string attemptName = newName;
+ int attempt = 0;
+ while (true)
+ {
+ int i = 0;
+ for (i = 0; i < GetParameterCount(); i++)
+ {
+ if (attemptName == GetParameter(i)->GetName())
+ {
+ attemptName = newName;
+ attemptName += Format(" %d", attempt);
+ attempt++;
+ break;
+ }
+ }
+ if (i == GetParameterCount())
+ break;
+ }
+
+ return attemptName;
+}
+
+string AnimatorController::MakeUniqueLayerName(const string& newName) const
+{
+ string attemptName = newName;
+ int attempt = 0;
+ while (true)
+ {
+ int i = 0;
+ for (i = 0; i < GetLayerCount(); i++)
+ {
+ if (attemptName == GetLayer(i)->GetName())
+ {
+ attemptName = newName;
+ attemptName += Format(" %d", attempt);
+ attempt++;
+ break;
+ }
+ }
+ if (i == GetLayerCount())
+ break;
+ }
+
+ return attemptName;
+
+}
+
+
+bool AnimatorController::ValidateLayerIndex(int index) const
+{
+ if(index >= 0 && index < GetLayerCount())
+ {
+ return true;
+ }
+
+ ErrorString("Invalid Layer index");
+ return false;
+}
+
+bool AnimatorController::ValidateParameterIndex(int index) const
+{
+ if(index >= 0 && index < GetParameterCount())
+ {
+ return true;
+ }
+
+ ErrorString("Invalid Parameter index");
+ return false;
+}
+
+
+#endif
+
+
+AnimationClipVector AnimatorController::GetAnimationClips() const
+{
+ const_cast<AnimatorController*>(this)->GetAsset(); // @TODO: Force load the AnimatorController. This is kind of a hack.
+
+ return m_AnimationClips;
+}
+
+AnimationClipVector AnimatorController::GetAnimationClipsToRegister() const
+{
+ return GetAnimationClips();
+}
+
+
+
+void AnimatorController::OnInvalidateAnimatorController()
+{
+#if UNITY_EDITOR
+ if( !m_IsAssetBundled)
+ {
+ ClearAsset();
+
+ ScriptingInvocation invocation("UnityEditorInternal","AnimatorController", "OnInvalidateAnimatorController");
+ invocation.AddObject(Scripting::ScriptingWrapperFor(this));
+ invocation.Invoke();
+ }
+
+#endif
+
+ NotifyObjectUsers( kDidModifyAnimatorController );
+}
+
+mecanim::animation::ControllerConstant* AnimatorController::GetAsset()
+{
+#if UNITY_EDITOR
+ if (m_Controller == 0)
+ BuildAsset();
+#endif
+
+ return m_Controller;
+}
+
+UnityEngine::Animation::AnimationSetBindings* AnimatorController::GetAnimationSetBindings()
+{
+#if UNITY_EDITOR
+ if (m_AnimationSetBindings == 0)
+ BuildAsset();
+#endif
+
+ return m_AnimationSetBindings;
+}
+
+
+void AnimatorController::ClearAsset()
+{
+ m_AnimationSetBindings = NULL;
+ m_Controller = NULL;
+ m_TOS.clear();
+ m_Allocator.Reset();
+
+ m_AnimationClips.clear();
+}
+
+std::string AnimatorController::StringFromID(unsigned int id) const
+{
+ TOSVector::const_iterator it = m_TOS.find(id);
+ if(it != m_TOS.end())
+ return it->second;
+ return "";
+}
+
+
+#undef DIRTY_AND_INVALIDATE
diff --git a/Runtime/Animation/AnimatorController.h b/Runtime/Animation/AnimatorController.h
new file mode 100644
index 0000000..493657f
--- /dev/null
+++ b/Runtime/Animation/AnimatorController.h
@@ -0,0 +1,121 @@
+
+#ifndef EDITABLEAVATARCONTROLLER_H
+#define EDITABLEAVATARCONTROLLER_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "Runtime/BaseClasses/RefCounted.h"
+#include "Runtime/mecanim/memory.h"
+#include "Runtime/Animation/MecanimUtility.h"
+#include "Runtime/Animation/RuntimeAnimatorController.h"
+#include "Runtime/Misc/UserList.h"
+
+
+#if UNITY_EDITOR
+#include "Editor/Src/Animation/AnimatorControllerParameter.h"
+#include "Editor/Src/Animation/AnimatorControllerLayer.h"
+
+typedef std::vector<AnimatorControllerLayer> AnimatorControllerLayerVector;
+typedef std::vector<AnimatorControllerParameter> AnimatorControllerParameterVector;
+
+#endif
+
+template<class T>
+class PPtr;
+class AnimationClip;
+class StateMachine;
+class AvatarMask;
+
+class AnimatorController : public RuntimeAnimatorController
+{
+
+public :
+ REGISTER_DERIVED_CLASS (AnimatorController, RuntimeAnimatorController)
+ DECLARE_OBJECT_SERIALIZE (AnimatorController)
+
+ AnimatorController (MemLabelId label, ObjectCreationMode mode);
+
+ static void InitializeClass ();
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+ virtual void CheckConsistency ();
+
+#if UNITY_EDITOR
+
+ //////////////////////////////////////////
+ // AnimatorControllerLayers
+ AnimatorControllerLayer* GetLayer(int index);
+ const AnimatorControllerLayer* GetLayer(int index)const ;
+ int GetLayerCount()const;
+ void AddLayer(const std::string& name);
+ void RemoveLayer(int index);
+
+ //////////////////////////////////////////
+ // AnimatorControllerParameter
+ AnimatorControllerParameter* GetParameter(int index) ;
+ const AnimatorControllerParameter* GetParameter(int index) const;
+ int GetParameterCount() const ;
+ void AddParameter(const std::string& name, AnimatorControllerParameterType type);
+ void RemoveParameter(int index);
+ int FindParameter(const std::string& name) const;
+
+
+ std::vector<PPtr<Object> > CollectObjectsUsingParameter(const string& parameterName);
+ /////////////////////////////////////////////
+
+ string MakeUniqueParameterName(const string& newName) const;
+ string MakeUniqueLayerName(const string& newName) const;
+
+ bool ValidateLayerIndex(int index) const;
+ bool ValidateParameterIndex(int index) const;
+
+
+private:
+
+ bool ValidAnimationSet();
+ void BuildAsset();
+
+
+ AnimatorControllerLayerVector m_AnimatorLayers;
+ AnimatorControllerParameterVector m_AnimatorParameters;
+ UserList m_Dependencies;
+
+
+ template<class T>
+ void ParametersAndLayersBackwardsCompatibility (T& transfer);
+
+ AnimatorControllerLayer* CreateAnimatorControllerLayer();
+ AnimatorControllerParameter* CreateAnimatorControllerParameter();
+
+#endif // UNITY_EDITOR
+
+public:
+
+ virtual UnityEngine::Animation::AnimationSetBindings* GetAnimationSetBindings();
+ virtual AnimationClipVector GetAnimationClips() const ;
+ virtual mecanim::animation::ControllerConstant* GetAsset();
+
+
+ virtual std::string StringFromID(unsigned int ID) const;
+ void OnInvalidateAnimatorController();
+ bool IsAssetBundled() { return m_IsAssetBundled; }
+
+private :
+
+ void ClearAsset();
+ void OnAnimationClipDeleted();
+ virtual AnimationClipVector GetAnimationClipsToRegister() const;
+
+
+ AnimationClipVector m_AnimationClips;
+ bool m_IsAssetBundled;
+
+ mecanim::memory::ChainedAllocator m_Allocator;
+ mecanim::animation::ControllerConstant* m_Controller;
+ UInt32 m_ControllerSize;
+ UnityEngine::Animation::AnimationSetBindings* m_AnimationSetBindings;
+ TOSVector m_TOS;
+};
+
+
+#endif //EDITABLEAVATARCONTROLLER_H
diff --git a/Runtime/Animation/AnimatorGenericBindings.cpp b/Runtime/Animation/AnimatorGenericBindings.cpp
new file mode 100644
index 0000000..78d884f
--- /dev/null
+++ b/Runtime/Animation/AnimatorGenericBindings.cpp
@@ -0,0 +1,1100 @@
+#include "UnityPrefix.h"
+#include "AnimatorGenericBindings.h"
+#include "AnimationClipBindings.h"
+#include "AnimationSetBinding.h"
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+#include "Runtime/Animation/MecanimUtility.h"
+#include "Runtime/Animation/AnimationUtility.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "GenericAnimationBindingCache.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/BaseClasses/EventIDs.h"
+
+namespace UnityEngine { namespace Animation
+{
+ static void InitializeDefaultValues (const UnityEngine::Animation::AnimatorGenericBindingConstant& genericBinding, const mecanim::animation::AvatarConstant* avatar, bool hasTransformHierarchy, mecanim::animation::ControllerBindingConstant& controllerBindingConstant);
+
+ struct BoundTransform
+ {
+ BindingHash pathHash;
+
+ Transform* transform;
+ int bindIndexForSkeleton;
+ };
+
+ #define IS_DEPRECATED_NAME_BASED_BINDING (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+
+ mecanim::int32_t SkeletonFindNodeIndexByNameID(const mecanim::animation::AvatarConstant* avatar, mecanim::uint32_t aNameID)
+ {
+ mecanim::int32_t ret = -1;
+
+ mecanim::int32_t i;
+ for(i = 0; ret == -1 && i < avatar->m_SkeletonNameIDCount; i++)
+ {
+ if(avatar->m_SkeletonNameIDArray[i] == aNameID)
+ {
+ ret = i;
+ }
+ }
+
+ return ret;
+ }
+
+
+
+ void GenerateTransformBindingMapRecursive (Transform& transform, const mecanim::crc32& nameHash, dynamic_array<BoundTransform>& bindings, const mecanim::animation::AvatarConstant* avatar, bool hasTransformHierarchy)
+ {
+ const mecanim::skeleton::Skeleton* skeleton = avatar->m_AvatarSkeleton.Get();
+
+ BoundTransform& binding = bindings.push_back();
+ binding.pathHash = nameHash.checksum();
+ binding.transform = &transform;
+
+ if (IS_DEPRECATED_NAME_BASED_BINDING)
+ {
+ binding.pathHash = mecanim::processCRC32(transform.GetName());
+ }
+
+ if (hasTransformHierarchy)
+ {
+ // binding.pathHash : full path
+ binding.bindIndexForSkeleton = skeleton ? mecanim::skeleton::SkeletonFindNode(skeleton, binding.pathHash) : -1;
+ }
+ else
+ {
+ // binding.pathHash : flattened path
+ binding.bindIndexForSkeleton = SkeletonFindNodeIndexByNameID(avatar, binding.pathHash);
+ }
+
+ Transform::iterator end=transform.end();
+ for (Transform::iterator i=transform.begin();i != end;++i)
+ {
+ Transform* child = *i;
+ const char* name = child->GetName();
+
+ mecanim::crc32 childNameHash = AppendPathToHash(nameHash, name);
+ GenerateTransformBindingMapRecursive(*child, childNameHash, bindings, avatar, hasTransformHierarchy);
+ }
+ }
+
+ int FindTransformBindingIndexByBindingHash (const dynamic_array<BoundTransform>& bindings, BindingHash pathHash)
+ {
+ for (int i=0;i<bindings.size();++i)
+ {
+ if (bindings[i].pathHash == pathHash)
+ return i;
+ }
+
+ return -1;
+ }
+
+ int FindTransformBindingIndexBySkeletonIndex (const dynamic_array<BoundTransform>& bindings, int skeletonIndex)
+ {
+ for (int i=0;i<bindings.size();++i)
+ {
+ if (bindings[i].bindIndexForSkeleton == skeletonIndex)
+ return i;
+ }
+
+ return -1;
+ }
+
+ static void BindControllerRootMotionMask(const mecanim::animation::AvatarConstant &avatar,const mecanim::animation::ControllerConstant &controller, bool *rootMotionLayerMask)
+ {
+ mecanim::uint32_t rootMotionNodePathHash = 0;
+
+ if(avatar.m_RootMotionBoneIndex != -1)
+ {
+ rootMotionNodePathHash = avatar.m_AvatarSkeleton->m_ID[avatar.m_RootMotionBoneIndex];
+ }
+
+ for(int layerIter = 0; layerIter < controller.m_LayerCount; layerIter++)
+ {
+ bool mask = false;
+ bool found = false;
+
+ for(int maskIter = 0; !found && maskIter < controller.m_LayerArray[layerIter]->m_SkeletonMask->m_Count; maskIter++)
+ {
+ found = controller.m_LayerArray[layerIter]->m_SkeletonMask->m_Data[maskIter].m_PathHash == rootMotionNodePathHash;
+
+ if(found)
+ {
+ mask = controller.m_LayerArray[layerIter]->m_SkeletonMask->m_Data[maskIter].m_Weight > 0;
+ }
+ }
+
+ rootMotionLayerMask[layerIter] = mask;
+ }
+ }
+
+ mecanim::animation::ControllerBindingConstant *CreateControllerBindingConstant(const mecanim::animation::ControllerConstant* controller, const mecanim::animation::AnimationSet* animationSet, mecanim::ValueArrayConstant* valueArrayConstant, mecanim::uint32_t valueArrayConstantSize, const mecanim::animation::AvatarConstant* avatar, mecanim::memory::Allocator& alloc)
+ {
+ SETPROFILERLABEL(ControllerBindingConstant);
+
+ mecanim::animation::ControllerBindingConstant* controllerBindingConstant = alloc.Construct<mecanim::animation::ControllerBindingConstant>();
+ controllerBindingConstant->m_Avatar = avatar;
+ controllerBindingConstant->m_Controller = controller;
+ controllerBindingConstant->m_AnimationSet = animationSet;
+
+ int skeletonCount = !avatar->m_AvatarSkeleton.IsNull() ? avatar->m_AvatarSkeleton->m_Count : 0;
+ if (skeletonCount > 0)
+ controllerBindingConstant->m_SkeletonTQSMap = alloc.ConstructArray<mecanim::animation::SkeletonTQSMap> (avatar->m_AvatarSkeleton->m_Count);
+
+ controllerBindingConstant->m_DynamicValuesConstant = CreateValueArrayConstantCopy (valueArrayConstant, valueArrayConstantSize, alloc);
+ controllerBindingConstant->m_DynamicValuesDefault = CreateValueArray(controllerBindingConstant->m_DynamicValuesConstant, alloc);
+
+ controllerBindingConstant->m_RootMotionLayerMask = alloc.ConstructArray<bool>(controller->m_LayerCount);
+ BindControllerRootMotionMask(*avatar,*controller,controllerBindingConstant->m_RootMotionLayerMask);
+
+ return controllerBindingConstant;
+ }
+
+ void DestroyControllerBindingConstant(mecanim::animation::ControllerBindingConstant* controllerBindingConstant, mecanim::memory::Allocator& alloc)
+ {
+ if(controllerBindingConstant)
+ {
+ DestroyValueArray (controllerBindingConstant->m_DynamicValuesDefault, alloc);
+ DestroyValueArrayConstant (controllerBindingConstant->m_DynamicValuesConstant, alloc);
+ alloc.Deallocate (controllerBindingConstant->m_SkeletonTQSMap);
+ alloc.Deallocate (controllerBindingConstant->m_RootMotionLayerMask);
+ alloc.Deallocate (controllerBindingConstant);
+ }
+ }
+
+ static void BindControllerTQSMap(const AnimationSetBindings& animationSetBindings,
+ const mecanim::skeleton::Skeleton& skeleton,
+ int nonConstantTransformBindingCount,
+ const int* genericTransformBindingToBindingCache,
+ const BoundTransform* bindingCache,
+ bool hasTransformHierarchy,
+ mecanim::animation::ControllerBindingConstant *binding,
+ mecanim::memory::Allocator& alloc)
+ {
+ if (binding->m_SkeletonTQSMap == NULL)
+ return;
+
+ int rotationCount = -1;
+ int positionCount = -1;
+ int scaleCount = -1;
+
+ for (int transformIter = 0; transformIter < nonConstantTransformBindingCount; transformIter++)
+ {
+ const TransformBinding& transformBinding = animationSetBindings.transformBindings[transformIter];
+ int targetType = transformBinding.bindType;
+
+ if (targetType == kBindTransformScale)
+ scaleCount++;
+ else if (targetType == kBindTransformRotation)
+ rotationCount++;
+ else if (targetType == kBindTransformPosition)
+ positionCount++;
+
+ int skIndex = -1;
+ if (hasTransformHierarchy)
+ {
+ int transformIndex = genericTransformBindingToBindingCache[transformIter];
+ if (transformIndex == -1)
+ continue;
+ skIndex = bindingCache[transformIndex].bindIndexForSkeleton;
+ }
+ else
+ skIndex = SkeletonFindNode(&skeleton, transformBinding.path);
+
+ if (skIndex == -1)
+ continue;
+
+ if(targetType == kBindTransformScale)
+ binding->m_SkeletonTQSMap[skIndex].m_SIndex = scaleCount;
+ else if(targetType == kBindTransformRotation)
+ binding->m_SkeletonTQSMap[skIndex].m_QIndex = rotationCount;
+ else if(targetType == kBindTransformPosition)
+ binding->m_SkeletonTQSMap[skIndex].m_TIndex = positionCount;
+ }
+ }
+
+ static void GetDefaultTransformValues (Transform& targetTransform, int bindType, float* defaults)
+ {
+ if (bindType == kBindTransformPosition)
+ {
+ Vector3f pos = targetTransform.GetLocalPosition();
+ memcpy(defaults, &pos, sizeof(pos));
+ }
+ else if (bindType == kBindTransformRotation)
+ {
+ Quaternionf rot = targetTransform.GetLocalRotation();
+ memcpy(defaults, &rot, sizeof(rot));
+ }
+ else if (bindType == kBindTransformScale)
+ {
+ Vector3f scale = targetTransform.GetLocalScale();
+ memcpy(defaults, &scale, sizeof(scale));
+ }
+ else
+ {
+ AssertString("Bad");
+ }
+ }
+
+ static void GetDefaultSkeletonPoseValues (const math::xform& x, int bindType, float* defaults)
+ {
+ if (bindType == kBindTransformPosition)
+ math::store(x.t, defaults);
+ else if (bindType == kBindTransformRotation)
+ math::store(x.q, defaults);
+ else if (bindType == kBindTransformScale)
+ math::store(x.s, defaults);
+ else
+ {
+ AssertString("Bad");
+ }
+ }
+
+ static int CalculateTransformBindingSizeBasedOnConstantOptimization (const AnimationSetBindings& animationSet, dynamic_array<BoundTransform> const& transformBindingCache, const int* genericTransformBindingToBindingCache, const mecanim::animation::AvatarConstant* avatar, bool hasTransformHierarchy)
+ {
+ // Generate Constant Default values
+ ATTRIBUTE_ALIGN(ALIGN4F) float values[4];
+ const mecanim::skeleton::Skeleton* skeleton = hasTransformHierarchy ? NULL : avatar->m_AvatarSkeleton.Get();
+ const mecanim::skeleton::SkeletonPose* defaultPose = hasTransformHierarchy ? NULL : avatar->m_AvatarSkeletonPose.Get();
+
+ int highestMismatch = animationSet.transformBindingsNonConstantSize;
+
+ int constantDefaultValueIndex = 0;
+ for (int i=animationSet.transformBindingsNonConstantSize;i<animationSet.transformBindingsSize;i++)
+ {
+ const TransformBinding& transformBinding = animationSet.transformBindings[i];
+ int count = GetCurveCountForBindingType (transformBinding.bindType);
+
+ if (hasTransformHierarchy)
+ {
+ // If we can't write the value, then we don't need it to be sampled either...
+ // Thus we can simply assume it matches.
+ int transformIndex = genericTransformBindingToBindingCache[i];
+ if (transformIndex == -1)
+ {
+ constantDefaultValueIndex += count;
+ continue;
+ }
+ Transform* targetTransform = transformBindingCache[transformIndex].transform;
+ GetDefaultTransformValues(*targetTransform, transformBinding.bindType, values);
+ }
+ else
+ {
+ // get the default value from skeleton
+ int skeletonIndex = SkeletonFindNode(skeleton, transformBinding.path);
+ if (skeletonIndex == -1)
+ {
+ constantDefaultValueIndex += count;
+ continue;
+ }
+ GetDefaultSkeletonPoseValues(defaultPose->m_X[skeletonIndex], transformBinding.bindType, values);
+ }
+
+ for (int k=0;k<count;k++)
+ {
+ float clipConstant = animationSet.constantCurveValues[constantDefaultValueIndex];
+
+ if (!CompareApproximately(clipConstant, values[k], 0.00001F))
+ {
+ // printf_console("mismatch index: %d type: %d clipconstant/instance: %f vs %f.\n", i, bindType, animationSet.constantCurveValues[constantDefaultValueIndex], values[k]);
+ highestMismatch = i + 1;
+ }
+
+ constantDefaultValueIndex++;
+ }
+ }
+
+ Assert(constantDefaultValueIndex == animationSet.constantCurveValueCount);
+ return highestMismatch;
+ }
+
+ static void InvalidateBoundCurveArray(BoundCurve *boundCurveArray, int boundCurveCount, Object *object)
+ {
+ for(int iter = 0; iter < boundCurveCount; iter++)
+ {
+ if(boundCurveArray[iter].targetObject == object)
+ {
+ boundCurveArray[iter] = BoundCurve();
+ }
+ }
+ }
+
+ static void InvalidateTransformArray(Transform **transformArray, int transformCount, Object *object)
+ {
+ for(int iter = 0; iter < transformCount; iter++)
+ {
+ if(transformArray[iter] == object)
+ {
+ transformArray[iter] = 0;
+ }
+ }
+ }
+
+ void InvalidateAvatarBindingObject(AvatarBindingConstant* bindingConstant, Object *object)
+ {
+ InvalidateTransformArray(bindingConstant->skeletonBindings,bindingConstant->skeletonBindingsCount,object);
+ for (int i = 0; i < bindingConstant->exposedTransformCount; i++)
+ {
+ if (bindingConstant->exposedTransforms[i].transform == object)
+ bindingConstant->exposedTransforms[i].transform = NULL;
+ }
+ }
+
+
+ static void InvalidateGenericBindingObject(AnimatorGenericBindingConstant* bindingConstant, Object *object)
+ {
+ InvalidateBoundCurveArray(bindingConstant->transformBindings,bindingConstant->transformBindingsCount,object);
+ InvalidateBoundCurveArray(bindingConstant->genericBindings,bindingConstant->genericBindingsCount,object);
+ InvalidateBoundCurveArray(bindingConstant->genericPPtrBindings,bindingConstant->genericPPtrBindingsCount,object);
+ }
+
+ static void GenericBindingCallback(void *userData, void *sender,int eventType)
+ {
+ if(eventType == kWillDestroyEvent)
+ {
+ InvalidateGenericBindingObject(reinterpret_cast<AnimatorGenericBindingConstant *>(userData),reinterpret_cast<Object *>(sender));
+ }
+ }
+
+ void AvatarBindingCallback(void *userData, void *sender,int eventType)
+ {
+ if(eventType == kWillDestroyEvent)
+ {
+ InvalidateAvatarBindingObject(reinterpret_cast<AvatarBindingConstant *>(userData),reinterpret_cast<Object *>(sender));
+ }
+ }
+
+ static void RegisterBoundCurveArray(BoundCurve *boundCurveArray, int boundCurveCount, AnimatorGenericBindingConstant* bindingConstant)
+ {
+ for(int iter = 0; iter < boundCurveCount; iter++)
+ {
+ if(boundCurveArray[iter].targetObject != 0)
+ {
+ if(!boundCurveArray[iter].targetObject->HasEvent(GenericBindingCallback,bindingConstant))
+ {
+ boundCurveArray[iter].targetObject->AddEvent(GenericBindingCallback,bindingConstant);
+ }
+ }
+ }
+ }
+
+ template <typename TYPE> static void RegisterTransformArray(Transform **transformArray, int transformCount, TYPE* bindingConstant, Object::EventCallback* callback)
+ {
+ for(int iter = 0; iter < transformCount; iter++)
+ {
+ if(transformArray[iter] != 0)
+ {
+ if(!transformArray[iter]->HasEvent(callback, bindingConstant))
+ {
+ transformArray[iter]->AddEvent(callback, bindingConstant);
+ }
+ }
+ }
+ }
+
+ void RegisterAvatarBindingObjects(AvatarBindingConstant* bindingConstant)
+ {
+ RegisterTransformArray(bindingConstant->skeletonBindings,bindingConstant->skeletonBindingsCount,bindingConstant, AvatarBindingCallback);
+ for (int i = 0; i < bindingConstant->exposedTransformCount; i++)
+ {
+ if (bindingConstant->exposedTransforms[i].transform &&
+ !bindingConstant->exposedTransforms[i].transform->HasEvent(AvatarBindingCallback, bindingConstant))
+ bindingConstant->exposedTransforms[i].transform->AddEvent(AvatarBindingCallback, bindingConstant);
+ }
+ }
+
+ static void RegisterGenericBindingObjects(AnimatorGenericBindingConstant* bindingConstant)
+ {
+ RegisterBoundCurveArray(bindingConstant->transformBindings,bindingConstant->transformBindingsCount,bindingConstant);
+ RegisterBoundCurveArray(bindingConstant->genericBindings,bindingConstant->genericBindingsCount,bindingConstant);
+ RegisterBoundCurveArray(bindingConstant->genericPPtrBindings,bindingConstant->genericPPtrBindingsCount,bindingConstant);
+ }
+
+ static void UnregisterBoundCurveArray(BoundCurve *boundCurveArray, int boundCurveCount, AnimatorGenericBindingConstant* bindingConstant)
+ {
+ for(int iter = 0; iter < boundCurveCount; iter++)
+ {
+ if(boundCurveArray[iter].targetObject != 0)
+ {
+ boundCurveArray[iter].targetObject->RemoveEvent(GenericBindingCallback,bindingConstant);
+ }
+ }
+ }
+
+ template<typename TYPE> static void UnregisterTransformArray(Transform **transformArray, int transformCount, TYPE* bindingConstant, Object::EventCallback* callback)
+ {
+ for(int iter = 0; iter < transformCount; iter++)
+ {
+ if(transformArray[iter] != 0)
+ {
+ transformArray[iter]->RemoveEvent(callback,bindingConstant);
+ }
+ }
+ }
+
+ void UnregisterAvatarBindingObjects(AvatarBindingConstant* bindingConstant)
+ {
+ UnregisterTransformArray(bindingConstant->skeletonBindings,bindingConstant->skeletonBindingsCount,bindingConstant, AvatarBindingCallback);
+ for (int i = 0; i < bindingConstant->exposedTransformCount; i++)
+ {
+ if (bindingConstant->exposedTransforms[i].transform != NULL)
+ bindingConstant->exposedTransforms[i].transform->RemoveEvent(AvatarBindingCallback, bindingConstant);
+ }
+ }
+
+ void UnregisterGenericBindingObjects(AnimatorGenericBindingConstant* bindingConstant)
+ {
+ UnregisterBoundCurveArray(bindingConstant->transformBindings,bindingConstant->transformBindingsCount,bindingConstant);
+ UnregisterBoundCurveArray(bindingConstant->genericBindings,bindingConstant->genericBindingsCount,bindingConstant);
+ UnregisterBoundCurveArray(bindingConstant->genericPPtrBindings,bindingConstant->genericPPtrBindingsCount,bindingConstant);
+ }
+
+ static Transform *humanMark = reinterpret_cast<Transform *>(std::numeric_limits<size_t>::max());
+
+ static void humanMarkUp(mecanim::skeleton::Skeleton const &sk, int nodeIndex, Transform** bindings)
+ {
+ if(nodeIndex != -1)
+ {
+ bindings[nodeIndex] = humanMark;
+
+ humanMarkUp(sk,sk.m_Node[nodeIndex].m_ParentId,bindings);
+ }
+ }
+
+ AvatarBindingConstant* CreateAvatarBindingConstant (Transform& root, mecanim::animation::AvatarConstant const* avatar, mecanim::memory::Allocator& allocator)
+ {
+ SETPROFILERLABEL(AvatarBindingConstant);
+
+ // Generate binding cache
+ dynamic_array<BoundTransform> transformBindingCache (kMemTempAlloc);
+
+ const mecanim::skeleton::Skeleton* skeleton = avatar->m_AvatarSkeleton.Get();
+
+ GenerateTransformBindingMapRecursive(root, mecanim::crc32(), transformBindingCache, avatar, true);
+
+ AvatarBindingConstant* constant = allocator.Construct<AvatarBindingConstant> ();
+ constant->exposedTransformCount = 0;
+ constant->exposedTransforms = NULL;
+
+ constant->skeletonBindingsCount = skeleton ? skeleton->m_Count : 0;
+ constant->skeletonBindings = allocator.ConstructArray<Transform*> (constant->skeletonBindingsCount);
+
+ int transformChangedMask = 0;
+
+ // just bind what human will effectively affect
+
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_3_a1))
+ {
+ if (constant->skeletonBindingsCount != 0)
+ {
+ memset(constant->skeletonBindings, 0, sizeof(Transform*) * constant->skeletonBindingsCount);
+
+ if(avatar->m_HumanSkeletonIndexCount > 0)
+ {
+ humanMarkUp(*skeleton,avatar->m_HumanSkeletonIndexArray[0],constant->skeletonBindings);
+
+ for(int humanSkIndexIter = 0; humanSkIndexIter < avatar->m_HumanSkeletonIndexCount; humanSkIndexIter++)
+ {
+ int humanSkIndex = avatar->m_HumanSkeletonIndexArray[humanSkIndexIter];
+
+ if(humanSkIndex != -1)
+ {
+ constant->skeletonBindings[humanSkIndex] = humanMark;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ for(int i = 0 ; i < constant->skeletonBindingsCount; ++i)
+ constant->skeletonBindings[i] = humanMark;
+ }
+
+ for (int i=0;i<transformBindingCache.size();i++)
+ {
+ int skeletonIndex = transformBindingCache[i].bindIndexForSkeleton;
+
+ if (skeletonIndex != -1)
+ {
+ if(constant->skeletonBindings[skeletonIndex] == humanMark)
+ {
+ constant->skeletonBindings[skeletonIndex] = transformBindingCache[i].transform;
+ transformChangedMask |= Transform::kPositionChanged | Transform::kRotationChanged;
+ }
+ }
+ }
+
+ for(int i = 0 ; i < constant->skeletonBindingsCount; ++i)
+ {
+ if(constant->skeletonBindings[i] == humanMark)
+ constant->skeletonBindings[i] = 0;
+ }
+
+ constant->transformChangedMask = transformChangedMask;
+
+ RegisterAvatarBindingObjects(constant);
+
+ return constant;
+ }
+
+ AvatarBindingConstant* CreateAvatarBindingConstantOpt (Transform& root, mecanim::animation::AvatarConstant const* avatar, mecanim::memory::Allocator& allocator)
+ {
+ SETPROFILERLABEL(AvatarBindingConstant);
+
+ // Generate binding cache
+ dynamic_array<BoundTransform> transformBindingCache (kMemTempAlloc);
+
+ GenerateTransformBindingMapRecursive(root, mecanim::crc32(), transformBindingCache, avatar, false);
+
+ mecanim::skeleton::Skeleton const* skeleton = avatar->m_AvatarSkeleton.Get();
+
+ AvatarBindingConstant* constant = allocator.Construct<AvatarBindingConstant> ();
+ constant->skeletonBindingsCount = 0;
+ constant->skeletonBindings = NULL;
+ constant->transformChangedMask = 0;
+
+ // For the flattened transform, it's impossible to tell if the transform will be modified just by the curve which
+ // is binded to it, because all its parent transforms will also affect it.
+ // Since normally, there are only a few exposed transforms, the performance penalty will not be huge
+ // if we don't care which exact properties of the transform are modified.
+ int exposedCount = 0;
+ int maxExposeCount = transformBindingCache.size();
+ dynamic_array<ExposedTransform> exposedTransforms(maxExposeCount, kMemTempAlloc);
+ for (int i = 0; i < maxExposeCount; ++i)
+ {
+ BoundTransform& boundTransform = transformBindingCache[i];
+ bool isChildOfRoot = (boundTransform.transform->GetParent() == &root);
+ if (!isChildOfRoot)
+ continue;
+
+ ExposedTransform& exposedTransform = exposedTransforms[exposedCount];
+ exposedTransform.transform = boundTransform.transform;
+ exposedTransform.skeletonIndex = -1;
+ exposedTransform.skeletonIndexForUpdateTransform = -1;
+
+ if (boundTransform.bindIndexForSkeleton != -1)
+ {
+ exposedTransform.skeletonIndex = boundTransform.bindIndexForSkeleton;
+ exposedTransform.skeletonIndexForUpdateTransform = boundTransform.bindIndexForSkeleton;
+ }
+
+ // Handle special case: SkinnedMeshRenderer
+ // We directly update the root bone to the Transform of the SkinnedMeshRenderer.
+ SkinnedMeshRenderer* skin = boundTransform.transform->QueryComponent(SkinnedMeshRenderer);
+ if (skin)
+ {
+ const Mesh* mesh = skin->GetMesh();
+ if (mesh && mesh->GetRootBonePathHash() != 0)
+ {
+ int skinRootIndex = skeleton ? mecanim::skeleton::SkeletonFindNode(
+ skeleton, mesh->GetRootBonePathHash()) : -1;
+ if (skinRootIndex != -1)
+ exposedTransform.skeletonIndexForUpdateTransform = skinRootIndex;
+ }
+ }
+ if (exposedTransform.skeletonIndexForUpdateTransform != -1)
+ exposedCount++;
+ }
+
+ constant->exposedTransformCount = exposedCount;
+ constant->exposedTransforms = allocator.ConstructArray<ExposedTransform> (constant->exposedTransformCount);
+ for (int i=0; i<exposedCount; i++)
+ constant->exposedTransforms[i] = exposedTransforms[i];
+
+ RegisterAvatarBindingObjects(constant);
+
+ return constant;
+ }
+
+ void DestroyAvatarBindingConstant (AvatarBindingConstant* bindingConstant, mecanim::memory::Allocator& allocator)
+ {
+ if (bindingConstant != NULL)
+ {
+ UnregisterAvatarBindingObjects(bindingConstant);
+
+ allocator.Deallocate(bindingConstant->skeletonBindings);
+ allocator.Deallocate(bindingConstant->exposedTransforms);
+ allocator.Deallocate(bindingConstant);
+ }
+ }
+
+ AnimatorGenericBindingConstant* CreateAnimatorGenericBindings (const AnimationSetBindings& animationSet, Transform& root, const mecanim::animation::AvatarConstant* avatar, const mecanim::animation::ControllerConstant* controller, mecanim::memory::Allocator& allocator)
+ {
+ SETPROFILERLABEL(AnimatorGenericBindingConstant);
+
+ GenericAnimationBindingCache& bindingCache = GetGenericAnimationBindingCache ();
+
+ const mecanim::skeleton::Skeleton* skeleton = avatar->m_AvatarSkeleton.Get();
+
+ // Generate binding cache
+ dynamic_array<BoundTransform> transformBindingCache (kMemTempAlloc);
+ dynamic_array<int> genericTransformBindingToBindingCache (kMemTempAlloc);
+
+ GenerateTransformBindingMapRecursive(root, mecanim::crc32(), transformBindingCache, avatar, true);
+
+ // Map from animation set to binding cache index
+ genericTransformBindingToBindingCache.resize_uninitialized(animationSet.transformBindingsSize);
+ for (int i=0;i<animationSet.transformBindingsSize;i++)
+ genericTransformBindingToBindingCache[i] = FindTransformBindingIndexByBindingHash (transformBindingCache, animationSet.transformBindings[i].path);
+
+ // Calculate Transform bindings that are actually animating (Constant curve values can be removed if they match the default values)
+ // Generate new reduced ValueArrayCount from it.
+ int nonConstantTransformBindingCount = CalculateTransformBindingSizeBasedOnConstantOptimization (animationSet, transformBindingCache, genericTransformBindingToBindingCache.begin(), avatar, true);
+ int optimizedValueArrayConstantCount = animationSet.animationSet->m_DynamicFullValuesConstant->m_Count - (animationSet.transformBindingsSize - nonConstantTransformBindingCount);
+ bool allowConstantClipSamplingOptimization = nonConstantTransformBindingCount == animationSet.transformBindingsNonConstantSize;
+
+ AnimatorGenericBindingConstant* constant = allocator.Construct<AnimatorGenericBindingConstant> ();
+
+ constant->transformBindingsCount = nonConstantTransformBindingCount;
+ constant->transformBindings = allocator.ConstructArray<BoundCurve> (constant->transformBindingsCount);
+
+ constant->genericBindingsCount = animationSet.genericBindingsSize;
+ constant->genericBindings = allocator.ConstructArray<BoundCurve> (constant->genericBindingsCount);
+
+ constant->genericPPtrBindingsCount = animationSet.genericPPtrBindingsSize;
+ constant->genericPPtrBindings = allocator.ConstructArray<BoundCurve> (constant->genericPPtrBindingsCount);
+
+ constant->allowConstantClipSamplingOptimization = allowConstantClipSamplingOptimization;
+
+ int transformChangedMask = 0;
+
+ // Bind Transforms
+ for (int i=0;i<constant->transformBindingsCount;i++)
+ {
+ int transformIndex = genericTransformBindingToBindingCache[i];
+ constant->transformBindings[i].targetType = animationSet.transformBindings[i].bindType;
+ if (transformIndex != -1)
+ {
+ constant->transformBindings[i].targetObject = transformBindingCache[transformIndex].transform;
+
+ transformChangedMask |= Transform::kPositionChanged | Transform::kRotationChanged;
+ if (animationSet.transformBindings[i].bindType == kBindTransformScale)
+ transformChangedMask |= Transform::kScaleChanged;
+ }
+ else
+ {
+ constant->transformBindings[i].targetObject = NULL;
+ }
+ }
+
+ constant->transformChangedMask = transformChangedMask;
+
+ // Bind Generic properties
+ for (int i=0;i<constant->genericBindingsCount;i++)
+ {
+ constant->genericBindings[i].targetObject = NULL;
+ constant->genericBindings[i].targetType = kUnbound;
+
+ int index = FindTransformBindingIndexByBindingHash (transformBindingCache, animationSet.genericBindings[i].path);
+ if (index != -1)
+ bindingCache.BindGeneric(animationSet.genericBindings[i], *transformBindingCache[index].transform, constant->genericBindings[i]);
+ }
+
+ // Bind Generic PPtr properties
+ for (int i=0;i<constant->genericPPtrBindingsCount;i++)
+ {
+ constant->genericPPtrBindings[i].targetObject = NULL;
+ constant->genericPPtrBindings[i].targetType = kUnbound;
+
+ int index = FindTransformBindingIndexByBindingHash (transformBindingCache, animationSet.genericPPtrBindings[i].path);
+ if (index != -1)
+ bindingCache.BindPPtrGeneric(animationSet.genericPPtrBindings[i], *transformBindingCache[index].transform, constant->genericPPtrBindings[i]);
+ }
+
+ constant->controllerBindingConstant = CreateControllerBindingConstant (controller, animationSet.animationSet, animationSet.animationSet->m_DynamicFullValuesConstant, optimizedValueArrayConstantCount, avatar, allocator);
+
+ // Gravity weight should must be in both optimized and non-optimized ValueArray
+ Assert(animationSet.animationSet->m_GravityWeightIndex == -1 || animationSet.animationSet->m_GravityWeightIndex < constant->controllerBindingConstant->m_DynamicValuesDefault->m_FloatCount);
+
+ // Bind Controller skeleton to dynamic value array
+ BindControllerTQSMap(animationSet, *skeleton, nonConstantTransformBindingCount, genericTransformBindingToBindingCache.begin(), transformBindingCache.begin(), true, constant->controllerBindingConstant, allocator);
+
+ RegisterGenericBindingObjects(constant);
+
+ InitializeDefaultValues (*constant, avatar, true, *constant->controllerBindingConstant);
+
+ return constant;
+ }
+
+ AnimatorGenericBindingConstant* CreateAnimatorGenericBindingsOpt ( const AnimationSetBindings& animationSet, Transform& root, const mecanim::animation::AvatarConstant* avatar, const mecanim::animation::ControllerConstant* controller, mecanim::memory::Allocator& allocator)
+ {
+ GenericAnimationBindingCache& bindingCache = GetGenericAnimationBindingCache ();
+ const mecanim::skeleton::Skeleton* skeleton = avatar->m_AvatarSkeleton.Get();
+
+ dynamic_array<BoundTransform> transformBindingCache (kMemTempAlloc);
+ // BoundTransform.pathHash: hash of flattened path
+ // BoundTransform.bindIndexForSkeleton: corresponding skeleton index
+ GenerateTransformBindingMapRecursive(root, mecanim::crc32(), transformBindingCache, avatar, false);
+
+ // Calculate Transform bindings that are actually animating (Constant curve values can be removed if they match the default values)
+ // Generate new reduced ValueArrayCount from it.
+ int nonConstantTransformBindingCount = CalculateTransformBindingSizeBasedOnConstantOptimization (animationSet, transformBindingCache, NULL, avatar, false);
+ int optimizedValueArrayConstantCount = animationSet.animationSet->m_DynamicFullValuesConstant->m_Count - (animationSet.transformBindingsSize - nonConstantTransformBindingCount);
+ bool allowConstantClipSamplingOptimization = nonConstantTransformBindingCount == animationSet.transformBindingsNonConstantSize;
+
+ AnimatorGenericBindingConstant* constant = allocator.Construct<AnimatorGenericBindingConstant> ();
+
+ constant->transformBindingsCount = 0;
+ constant->transformBindings = NULL;
+
+ constant->genericBindingsCount = animationSet.genericBindingsSize;
+ constant->genericBindings = allocator.ConstructArray<BoundCurve> (constant->genericBindingsCount);
+
+ constant->genericPPtrBindingsCount = animationSet.genericPPtrBindingsSize;
+ constant->genericPPtrBindings = allocator.ConstructArray<BoundCurve> (constant->genericPPtrBindingsCount);
+
+ constant->allowConstantClipSamplingOptimization = allowConstantClipSamplingOptimization;
+
+
+ // Bind Generic properties
+ for (int i=0; i<constant->genericBindingsCount; i++)
+ {
+ constant->genericBindings[i].targetObject = NULL;
+ constant->genericBindings[i].targetType = kUnbound;
+
+ BindingHash fullPath = animationSet.genericBindings[i].path;
+ int skeletonIndex = mecanim::skeleton::SkeletonFindNode(skeleton, fullPath);
+
+ int transformIndex = FindTransformBindingIndexBySkeletonIndex (transformBindingCache, skeletonIndex);
+ if (transformIndex != -1)
+ bindingCache.BindGeneric(animationSet.genericBindings[i], *transformBindingCache[transformIndex].transform, constant->genericBindings[i]);
+ }
+
+ // Bind Generic PPtr properties
+ for (int i=0; i<constant->genericPPtrBindingsCount; i++)
+ {
+ constant->genericPPtrBindings[i].targetObject = NULL;
+ constant->genericPPtrBindings[i].targetType = kUnbound;
+
+ BindingHash fullPath = animationSet.genericPPtrBindings[i].path;
+ int skeletonIndex = mecanim::skeleton::SkeletonFindNode(skeleton, fullPath);
+
+ int transformIndex = FindTransformBindingIndexBySkeletonIndex (transformBindingCache, skeletonIndex);
+ if (transformIndex != -1)
+ bindingCache.BindPPtrGeneric(animationSet.genericPPtrBindings[i], *transformBindingCache[transformIndex].transform, constant->genericPPtrBindings[i]);
+ }
+
+ constant->controllerBindingConstant = CreateControllerBindingConstant (controller, animationSet.animationSet, animationSet.animationSet->m_DynamicFullValuesConstant, optimizedValueArrayConstantCount, avatar, allocator);
+
+ // Gravity weight should must be in both optimized and non-optimized ValueArray
+ Assert(animationSet.animationSet->m_GravityWeightIndex == -1 || animationSet.animationSet->m_GravityWeightIndex < constant->controllerBindingConstant->m_DynamicValuesDefault->m_FloatCount);
+
+ // Bind Controller skeleton to dynamic value array
+ BindControllerTQSMap(animationSet, *skeleton, nonConstantTransformBindingCount, NULL, transformBindingCache.begin(), false, constant->controllerBindingConstant, allocator);
+
+ RegisterGenericBindingObjects(constant);
+
+ InitializeDefaultValues (*constant, avatar, false, *constant->controllerBindingConstant);
+
+ return constant;
+ }
+
+ void DestroyAnimatorGenericBindings (AnimatorGenericBindingConstant* bindingConstant, mecanim::memory::Allocator& allocator)
+ {
+ if (bindingConstant != NULL)
+ {
+ UnregisterGenericBindingObjects(bindingConstant);
+
+ DestroyControllerBindingConstant(bindingConstant->controllerBindingConstant, allocator);
+ allocator.Deallocate(bindingConstant->transformBindings);
+ allocator.Deallocate(bindingConstant->genericBindings);
+ allocator.Deallocate(bindingConstant);
+ }
+ }
+
+ ////// Get & Set Values
+ void SetGenericPPtrPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray &values)
+ {
+ for (int bindIndex = 0;bindIndex != bindings.genericPPtrBindingsCount;bindIndex++)
+ {
+ const BoundCurve& binding = bindings.genericPPtrBindings[bindIndex];
+ int targetType = binding.targetType;
+
+ if (targetType == kUnbound)
+ continue;
+
+ Assert (targetType >= kMinSinglePropertyBinding);
+
+ mecanim::int32_t value = 0;
+ values.ReadData(value, bindIndex);
+ SetBoundCurveIntValue (binding, value);
+ }
+ }
+
+ void SetGenericFloatPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray &values)
+ {
+ Object* lastAwakeFromLoadObject = NULL;
+ for (int bindIndex = 0;bindIndex != bindings.genericBindingsCount;bindIndex++)
+ {
+ const BoundCurve& binding = bindings.genericBindings[bindIndex];
+ int targetType = binding.targetType;
+
+ if (targetType == kUnbound)
+ continue;
+
+ // When applying multiple properties to the same object in a row.
+ // Call AwakeFromLoad / SetDirty only once.
+ if (ShouldAwakeGeneric(binding))
+ {
+ if (lastAwakeFromLoadObject != binding.targetObject)
+ {
+ if (lastAwakeFromLoadObject != NULL)
+ BoundCurveValueAwakeGeneric(*lastAwakeFromLoadObject);
+ lastAwakeFromLoadObject = binding.targetObject;
+ }
+ }
+
+ Assert (targetType >= kMinSinglePropertyBinding);
+
+ float value = 0.0F;
+ values.ReadData(value, bindIndex);
+
+ SetBoundCurveFloatValue (binding, value);
+ }
+
+ if (lastAwakeFromLoadObject != NULL)
+ BoundCurveValueAwakeGeneric(*lastAwakeFromLoadObject);
+ }
+
+
+ void SetTransformPropertyApplyMainThread (Transform& root, const AvatarBindingConstant& avatarBindings, bool skipRoot, int mask)
+ {
+ // Send TransformChanged message
+ int transformChangedMask = avatarBindings.transformChangedMask & mask;
+ if (transformChangedMask != 0)
+ {
+ if(skipRoot)
+ {
+ for(int childIter = 0; childIter < root.m_Children.size(); childIter++)
+ {
+ root.m_Children[childIter]->SendTransformChanged(transformChangedMask);
+ }
+ }
+ else
+ {
+ root.SendTransformChanged(transformChangedMask);
+ }
+ }
+
+ // In The editor we set dirty so the inspector UI is always up to date
+ #if UNITY_EDITOR
+ for (int bindIndex = 0;bindIndex != avatarBindings.skeletonBindingsCount;bindIndex++)
+ {
+ Transform* targetTransform = avatarBindings.skeletonBindings[bindIndex];
+ if (targetTransform)
+ targetTransform->SetDirty();
+ }
+ #endif
+ }
+
+ void SetTransformPropertyApplyMainThread (Transform& root, const AnimatorGenericBindingConstant& bindings, const AvatarBindingConstant& avatarBindings, bool skipRoot)
+ {
+ // Send TransformChanged message
+ if (bindings.transformChangedMask != 0)
+ {
+ if(skipRoot)
+ {
+ for(int childIter = 0; childIter < root.m_Children.size(); childIter++)
+ {
+ root.m_Children[childIter]->SendTransformChanged(bindings.transformChangedMask);
+ }
+ }
+ else
+ {
+ root.SendTransformChanged(bindings.transformChangedMask);
+ }
+ }
+
+ SetTransformPropertyApplyMainThread(root, avatarBindings, skipRoot, ~bindings.transformChangedMask);
+
+ // In The editor we set dirty so the inspector UI is always up to date
+ #if UNITY_EDITOR
+ for (int bindIndex = 0;bindIndex != bindings.transformBindingsCount;bindIndex++)
+ {
+ const BoundCurve& binding = bindings.transformBindings[bindIndex];
+ Transform* targetTransform = reinterpret_cast<Transform*> (binding.targetObject);
+ if (targetTransform)
+ targetTransform->SetDirty();
+ }
+ #endif
+ }
+
+
+ void SetHumanTransformPropertyValues (const AvatarBindingConstant& bindings, const mecanim::skeleton::SkeletonPose& pose)
+ {
+ int transformCount = bindings.skeletonBindingsCount;
+ Assert(pose.m_Count == transformCount);
+
+ // skip root node i = 1
+ for(int i = 1; i < transformCount; i++)
+ {
+ Transform* transform = bindings.skeletonBindings[i];
+ if (transform != NULL)
+ {
+ Vector3f t = float4ToVector3f(pose.m_X[i].t);
+ transform->SetLocalPositionWithoutNotification(t);
+
+ Quaternionf q = float4ToQuaternionf(pose.m_X[i].q);
+ transform->SetLocalRotationWithoutNotification(q);
+ }
+ }
+ }
+
+ void SetFlattenedSkeletonTransformsMainThread (const AvatarBindingConstant& bindings, const mecanim::skeleton::SkeletonPose& globalSpacePose, const mecanim::animation::AvatarConstant& avatar)
+ {
+ for (size_t i=0;i < bindings.exposedTransformCount;i++)
+ {
+ ExposedTransform& exposedTransform = bindings.exposedTransforms[i];
+ if (exposedTransform.transform)
+ {
+ const math::xform& globalXForm = globalSpacePose.m_X[exposedTransform.skeletonIndexForUpdateTransform];
+ exposedTransform.transform->SetPositionAndRotation(float4ToVector3f(globalXForm.t), float4ToQuaternionf(globalXForm.q));
+ }
+ }
+ }
+
+ void SetGenericTransformPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray &values, Transform *skipTransform)
+ {
+ int rotationIndex = 0;
+ int positionIndex = 0;
+ int scaleIndex = 0;
+
+ for (int bindIndex = 0;bindIndex != bindings.transformBindingsCount;bindIndex++)
+ {
+ const BoundCurve& binding = bindings.transformBindings[bindIndex];
+ int targetType = binding.targetType;
+
+ Transform* targetTransform = reinterpret_cast<Transform*> (binding.targetObject);
+
+ if (targetType == kBindTransformRotation)
+ {
+ if(targetTransform && (targetTransform != skipTransform ))
+ {
+ math::float4 value = values.ReadQuaternion(rotationIndex);
+ targetTransform->SetLocalRotationWithoutNotification(float4ToQuaternionf(value));
+ }
+ rotationIndex++;
+ }
+ else if (targetType == kBindTransformPosition)
+ {
+ if(targetTransform && (targetTransform != skipTransform ))
+ {
+ math::float4 value = values.ReadPosition(positionIndex);
+ targetTransform->SetLocalPositionWithoutNotification(float4ToVector3f(value));
+ }
+
+ positionIndex++;
+ }
+ else if (targetType == kBindTransformScale)
+ {
+ if(targetTransform && (targetTransform != skipTransform ))
+ {
+ math::float4 value = values.ReadScale(scaleIndex);
+ targetTransform->SetLocalScaleWithoutNotification(float4ToVector3f(value));
+ }
+
+ scaleIndex++;
+ }
+ }
+ }
+
+
+ static void GetDefaultTransformValues (const BoundCurve* transformBindings, size_t transformBindingsCount, mecanim::ValueArray &values)
+ {
+ int positionIndex = 0;
+ int rotationIndex = 0;
+ int scaleIndex = 0;
+
+ for (int bindIndex = 0; bindIndex < transformBindingsCount; bindIndex++)
+ {
+ const BoundCurve& binding = transformBindings[bindIndex];
+
+ Transform* targetTransform = reinterpret_cast<Transform*> (binding.targetObject);
+ if (binding.targetType == kBindTransformPosition)
+ {
+ if(targetTransform)
+ values.WritePosition(Vector3fTofloat4(targetTransform->GetLocalPosition()), positionIndex);
+
+ positionIndex++;
+ }
+ else if (binding.targetType == kBindTransformRotation)
+ {
+ if(targetTransform)
+ values.WriteQuaternion(QuaternionfTofloat4(targetTransform->GetLocalRotation()), rotationIndex);
+
+ rotationIndex++;
+ }
+ else if (binding.targetType == kBindTransformScale)
+ {
+ if(targetTransform)
+ values.WriteScale(Vector3fTofloat4(targetTransform->GetLocalScale()), scaleIndex);
+
+ scaleIndex++;
+ }
+ }
+ }
+
+ static void GetDefaultGenericFloatValues (const AnimatorGenericBindingConstant& bindings, mecanim::ValueArray &values)
+ {
+ for (int bindIndex = 0; bindIndex < bindings.genericBindingsCount; bindIndex++)
+ {
+ const BoundCurve& binding = bindings.genericBindings[bindIndex];
+ int targetType = binding.targetType;
+ if (targetType == kUnbound)
+ continue;
+
+ Assert (targetType >= kMinSinglePropertyBinding);
+
+ float value = GetBoundCurveFloatValue (binding);
+
+ values.WriteData(value, bindIndex);
+ }
+ }
+
+ static void GetDefaultGenericPPtrValues (const AnimatorGenericBindingConstant& bindings, mecanim::ValueArray &values)
+ {
+ for (int bindIndex = 0; bindIndex < bindings.genericPPtrBindingsCount; bindIndex++)
+ {
+ const BoundCurve& binding = bindings.genericPPtrBindings[bindIndex];
+ int targetType = binding.targetType;
+ if (targetType == kUnbound)
+ continue;
+
+ Assert (targetType >= kMinSinglePropertyBinding);
+
+ mecanim::int32_t value = GetBoundCurveIntValue (binding);
+
+ values.WriteData(value, bindIndex);
+ }
+ }
+
+ static void InitializeDefaultValues (const UnityEngine::Animation::AnimatorGenericBindingConstant& genericBinding, const mecanim::animation::AvatarConstant* avatar, bool hasTransformHierarchy, mecanim::animation::ControllerBindingConstant& controllerBindingConstant)
+ {
+ const mecanim::skeleton::Skeleton* skeleton = avatar->m_AvatarSkeleton.Get();
+ const mecanim::skeleton::SkeletonPose* skeletonPose = avatar->m_AvatarSkeletonPose.Get();
+ const mecanim::animation::AnimationSet& animSet = *controllerBindingConstant.m_AnimationSet;
+
+ if (hasTransformHierarchy)
+ {
+ // Get default values from transform
+ GetDefaultTransformValues (genericBinding.transformBindings, genericBinding.transformBindingsCount, *controllerBindingConstant.m_DynamicValuesDefault);
+ }
+ else
+ {
+ // When there is no transform & game object hierarchy, get it from the skeleton.
+ if (skeleton != NULL && skeletonPose != NULL)
+ ValueFromSkeletonPose (*skeleton, *skeletonPose, controllerBindingConstant.m_SkeletonTQSMap, *controllerBindingConstant.m_DynamicValuesDefault);
+ }
+
+ // Get default values from generic bindings
+ GetDefaultGenericFloatValues (genericBinding, *controllerBindingConstant.m_DynamicValuesDefault);
+ GetDefaultGenericPPtrValues (genericBinding, *controllerBindingConstant.m_DynamicValuesDefault);
+
+ // Copy default parameters from controller defaults to m_DynamicValuesDefault
+ const mecanim::animation::ControllerConstant* controller = controllerBindingConstant.m_Controller;
+ mecanim::ValueArrayReverseCopy(controller->m_Values.Get(), controller->m_DefaultValues.Get(), controllerBindingConstant.m_DynamicValuesConstant, controllerBindingConstant.m_DynamicValuesDefault, animSet.m_AdditionalIndexArray);
+ }
+
+} }
diff --git a/Runtime/Animation/AnimatorGenericBindings.h b/Runtime/Animation/AnimatorGenericBindings.h
new file mode 100644
index 0000000..4e2023b
--- /dev/null
+++ b/Runtime/Animation/AnimatorGenericBindings.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <limits>
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/mecanim/generic/valuearray.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+#include "Runtime/mecanim/animation/avatar.h"
+
+typedef UInt32 BindingHash;
+
+namespace Unity { class GameObject; }
+
+namespace UnityEngine
+{
+namespace Animation
+{
+ struct AnimationClipBindingConstant;
+ struct AnimationSetBindings;
+ struct BoundCurve;
+
+
+ struct ExposedTransform
+ {
+ Transform* transform;
+ int skeletonIndex;
+ int skeletonIndexForUpdateTransform;
+ };
+
+ struct AvatarBindingConstant
+ {
+ // For non-optimized mode (the Transform hierarchy is there)
+ size_t skeletonBindingsCount;
+ Transform** skeletonBindings;
+
+ int transformChangedMask;
+
+ // For optimized mode
+ size_t exposedTransformCount;
+ ExposedTransform* exposedTransforms;
+ };
+
+ struct AnimatorGenericBindingConstant
+ {
+ size_t transformBindingsCount;
+ BoundCurve* transformBindings;
+
+ size_t genericBindingsCount;
+ BoundCurve* genericBindings;
+
+ size_t genericPPtrBindingsCount;
+ BoundCurve* genericPPtrBindings;
+
+ int transformChangedMask;
+
+ mecanim::animation::ControllerBindingConstant* controllerBindingConstant;
+
+ bool allowConstantClipSamplingOptimization;
+ };
+
+ AvatarBindingConstant* CreateAvatarBindingConstant (Transform& root, const mecanim::animation::AvatarConstant* avatar, mecanim::memory::Allocator& allocator);
+ AvatarBindingConstant* CreateAvatarBindingConstantOpt (Transform& root, const mecanim::animation::AvatarConstant* avatar, mecanim::memory::Allocator& allocator);
+ void DestroyAvatarBindingConstant (AvatarBindingConstant* bindingConstant, mecanim::memory::Allocator& allocator);
+
+ /// Bind multiple animation clips against a GameObject
+ /// outputCurveIndexToBindingIndex is an array of arrays that remaps from the curve index of each clip into the bound curves array
+ /// returns a binding constant.
+ AnimatorGenericBindingConstant* CreateAnimatorGenericBindings (const AnimationSetBindings& animationSet, Transform& root, const mecanim::animation::AvatarConstant* avatar, const mecanim::animation::ControllerConstant* controller, mecanim::memory::Allocator& allocator);
+ AnimatorGenericBindingConstant* CreateAnimatorGenericBindingsOpt (const AnimationSetBindings& animationSet, Transform& root, const mecanim::animation::AvatarConstant* avatar, const mecanim::animation::ControllerConstant* controller, mecanim::memory::Allocator& allocator);
+ void DestroyAnimatorGenericBindings (AnimatorGenericBindingConstant* bindingConstant, mecanim::memory::Allocator& allocator);
+
+ /// Batch assign an array of values to the bound locations (Also calls AwakeFromLoad)
+ /// Must always be called from the main thread
+ void SetGenericFloatPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray& values);
+ void SetGenericPPtrPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray& values);
+
+ /// Batch assign transform properties to the bound Transform components
+ /// Can be invoked from another thread if we are sure no one will delete Transforms at the same time on another thread.
+ void SetGenericTransformPropertyValues (const AnimatorGenericBindingConstant& bindings, const mecanim::ValueArray& values, Transform *skipTransform);
+ void SetHumanTransformPropertyValues (const AvatarBindingConstant& bindings, const mecanim::skeleton::SkeletonPose& pose);
+
+ /// Invoke TransformChanged callbacks and SetDirty
+ /// Must always be called from the main thread
+ void SetTransformPropertyApplyMainThread (Transform& root, const AnimatorGenericBindingConstant& bindings, const AvatarBindingConstant& avatarBindings, bool skipRoot);
+ void SetTransformPropertyApplyMainThread (Transform& root, const AvatarBindingConstant& avatarBindings, bool skipRoot, int mask = std::numeric_limits<int>::max() );
+
+ /// Set transforms for optimized characters
+ /// Must always be called from the main thread
+ void SetFlattenedSkeletonTransformsMainThread (const AvatarBindingConstant& bindings, const mecanim::skeleton::SkeletonPose& globalSpacePose, const mecanim::animation::AvatarConstant& avatar);
+
+ void UnregisterAvatarBindingObjects(AvatarBindingConstant* bindingConstant);
+ void UnregisterGenericBindingObjects(AnimatorGenericBindingConstant* bindingConstant);
+
+}
+}
diff --git a/Runtime/Animation/AnimatorManager.cpp b/Runtime/Animation/AnimatorManager.cpp
new file mode 100644
index 0000000..2f76077
--- /dev/null
+++ b/Runtime/Animation/AnimatorManager.cpp
@@ -0,0 +1,110 @@
+#include "UnityPrefix.h"
+#include "AnimatorManager.h"
+#include "Animator.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Core/Callbacks/PlayerLoopCallbacks.h"
+
+static AnimatorManager* gAnimatorManager = NULL;
+
+void AnimatorManager::FixedUpdateFKMove()
+{
+ if(m_FixedUpdateAvatars.size() > 0 && GetTimeManager().GetFixedDeltaTime())
+ {
+ Animator::UpdateAvatars(m_FixedUpdateAvatars.begin(), m_FixedUpdateAvatars.size(), GetTimeManager().GetFixedDeltaTime(),true,false);
+ }
+}
+
+void AnimatorManager::FixedUpdateRetargetIKWrite()
+{
+ //@TODO: Why do we do FixedUpdateRetargetIKWrite?
+ // This visually wrong when you use physics interpolation to get smooth root motion, but then the animation of the characters limbs only updates at a very low physics framerate.
+ // It would be better to run UpdateRetargetWrite in the dynamic framerate.
+
+ if(m_FixedUpdateAvatars.size() > 0 && GetTimeManager().GetFixedDeltaTime())
+ {
+ Animator::UpdateAvatars(m_FixedUpdateAvatars.begin(), m_FixedUpdateAvatars.size(), GetTimeManager().GetFixedDeltaTime(),false,true);
+ }
+}
+
+void AnimatorManager::UpdateFKMove ()
+{
+ if(m_UpdateAvatars.size() > 0 && GetTimeManager().GetDeltaTime())
+ {
+ Animator::UpdateAvatars(m_UpdateAvatars.begin(), m_UpdateAvatars.size(), GetDeltaTime(),true,false);
+ }
+}
+
+void AnimatorManager::UpdateRetargetIKWrite ()
+{
+ if(m_UpdateAvatars.size() > 0 && GetTimeManager().GetDeltaTime())
+ {
+ Animator::UpdateAvatars(m_UpdateAvatars.begin(), m_UpdateAvatars.size(), GetDeltaTime(),false,true);
+ }
+}
+
+void AnimatorManager::AddAnimator (Animator& animator)
+{
+ if (animator.GetEnabled() && animator.IsActive() && animator.IsValid())
+ {
+ if(animator.GetAnimatePhysics())
+ {
+ animator.SetFixedBehaviourIndex(m_FixedUpdateAvatars.size());
+ m_FixedUpdateAvatars.push_back(&animator);
+ }
+ else
+ {
+ animator.SetBehaviourIndex(m_UpdateAvatars.size());
+ m_UpdateAvatars.push_back(&animator);
+ }
+ }
+}
+
+void AnimatorManager::RemoveAnimator (Animator& animator)
+{
+ int index = animator.GetBehaviourIndex();
+
+ if(index != -1)
+ {
+ Animator* backBehaviour = m_UpdateAvatars.back();
+ m_UpdateAvatars[index] = backBehaviour;
+ backBehaviour->SetBehaviourIndex(index);
+ animator.SetBehaviourIndex(-1);
+ m_UpdateAvatars.pop_back();
+ }
+
+ int fixedIndex = animator.GetFixedBehaviourIndex();
+
+ if(fixedIndex != -1)
+ {
+ Animator* backBehaviour = m_FixedUpdateAvatars.back();
+ m_FixedUpdateAvatars[fixedIndex] = backBehaviour;
+ backBehaviour->SetFixedBehaviourIndex(fixedIndex);
+ animator.SetFixedBehaviourIndex(-1);
+ m_FixedUpdateAvatars.pop_back();
+ }
+}
+
+void AnimatorManager::InitializeClass ()
+{
+ Assert(gAnimatorManager == NULL);
+ gAnimatorManager = UNITY_NEW_AS_ROOT (AnimatorManager, kMemAnimation, "AnimatorManager", "");
+
+ REGISTER_PLAYERLOOP_CALL (AnimatorFixedUpdateRetargetIKWrite, GetAnimatorManager().FixedUpdateRetargetIKWrite());
+ REGISTER_PLAYERLOOP_CALL (AnimatorUpdateRetargetIKWrite, GetAnimatorManager().UpdateRetargetIKWrite ());
+ REGISTER_PLAYERLOOP_CALL (AnimatorUpdateFKMove, GetAnimatorManager().UpdateFKMove ());
+ REGISTER_PLAYERLOOP_CALL (AnimatorFixedUpdateFKMove, GetAnimatorManager().FixedUpdateFKMove ());
+}
+
+void AnimatorManager::CleanupClass ()
+{
+ UNITY_DELETE (gAnimatorManager, kMemAnimation);
+ gAnimatorManager = NULL;
+
+
+}
+
+AnimatorManager& GetAnimatorManager ()
+{
+ Assert(gAnimatorManager != NULL);
+ return *gAnimatorManager;
+} \ No newline at end of file
diff --git a/Runtime/Animation/AnimatorManager.h b/Runtime/Animation/AnimatorManager.h
new file mode 100644
index 0000000..5e13463
--- /dev/null
+++ b/Runtime/Animation/AnimatorManager.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "Runtime/Utilities/dynamic_array.h"
+class Animator;
+
+class AnimatorManager
+{
+ public:
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ void AddAnimator(Animator& animator);
+ void RemoveAnimator(Animator& animator);
+
+ virtual void FixedUpdateFKMove();
+ virtual void FixedUpdateRetargetIKWrite();
+
+ virtual void UpdateFKMove();
+ virtual void UpdateRetargetIKWrite();
+
+private:
+
+ dynamic_array<Animator*> m_UpdateAvatars;
+ dynamic_array<Animator*> m_FixedUpdateAvatars;
+};
+
+AnimatorManager& GetAnimatorManager (); \ No newline at end of file
diff --git a/Runtime/Animation/AnimatorOverrideController.cpp b/Runtime/Animation/AnimatorOverrideController.cpp
new file mode 100644
index 0000000..2633ab4
--- /dev/null
+++ b/Runtime/Animation/AnimatorOverrideController.cpp
@@ -0,0 +1,302 @@
+#include "UnityPrefix.h"
+#include "AnimatorOverrideController.h"
+#include "AnimationSetBinding.h"
+#include "RuntimeAnimatorController.h"
+#include "AnimationClipBindings.h"
+#include "GenericAnimationBindingCache.h"
+#include "AnimationClip.h"
+
+#if UNITY_EDITOR
+#include "Runtime/Scripting/Scripting.h"
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+#endif
+
+#define DIRTY_AND_INVALIDATE() ClearAsset(); SetDirty(); NotifyObjectUsers( kDidModifyAnimatorController )
+
+struct FindClip
+{
+ char const* m_ClipName;
+ FindClip(char const* clipName):m_ClipName(clipName){}
+
+ bool operator()(PPtr<AnimationClip> const& clip){ return strcmp(clip->GetName(), m_ClipName) == 0 ; }
+};
+
+struct FindOriginalClipByName
+{
+ char const* m_ClipName;
+ FindOriginalClipByName(char const* clipName):m_ClipName(clipName){}
+
+ bool operator()(AnimationClipOverride const& overrideClip){ return strcmp(overrideClip.m_OriginalClip->GetName(), m_ClipName) == 0 ; }
+};
+
+struct FindOriginalClip
+{
+ PPtr<AnimationClip> const& m_Clip;
+ FindOriginalClip(PPtr<AnimationClip> const& clip):m_Clip(clip){}
+
+ bool operator()(AnimationClipOverride const& overrideClip){ return overrideClip.m_OriginalClip == m_Clip ; }
+};
+
+PPtr<AnimationClip> return_original(AnimationClipOverride const& overrideClip){ return overrideClip.m_OriginalClip; }
+PPtr<AnimationClip> return_override(AnimationClipOverride const& overrideClip){ return overrideClip.m_OverrideClip; }
+PPtr<AnimationClip> return_effective(AnimationClipOverride const& overrideClip){ return overrideClip.GetEffectiveClip(); }
+
+IMPLEMENT_OBJECT_SERIALIZE (AnimatorOverrideController)
+IMPLEMENT_CLASS_HAS_INIT (AnimatorOverrideController)
+
+INSTANTIATE_TEMPLATE_TRANSFER(AnimatorOverrideController)
+
+AnimatorOverrideController::AnimatorOverrideController(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode),
+m_AnimationSetBindings(0),
+m_AnimationSetNode(this),
+m_Allocator(label)
+{
+}
+
+AnimatorOverrideController::~AnimatorOverrideController()
+{
+}
+
+void AnimatorOverrideController::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+
+ if(!m_Controller.IsNull())
+ m_Controller->AddObjectUser(m_AnimationSetNode);
+
+ NotifyObjectUsers( kDidModifyAnimatorController );
+}
+
+void AnimatorOverrideController::InitializeClass ()
+{
+ REGISTER_MESSAGE_VOID(AnimatorOverrideController, kDidModifyAnimatorController, ClearAsset);
+ REGISTER_MESSAGE_VOID(AnimatorOverrideController, kDidModifyMotion, ClearAsset);
+}
+
+template<class Functor> PPtr<AnimationClip> AnimatorOverrideController::FindAnimationClipInMap(PPtr<AnimationClip> const& clip, Functor functor, PPtr<AnimationClip> const& defaultClip)const
+{
+ AnimationClipOverrideVector::const_iterator it = std::find_if(m_Clips.begin(), m_Clips.end(), FindOriginalClip(clip) );
+ return it != m_Clips.end() ? functor(*it) : defaultClip;
+}
+
+// Return the clip list from controller
+AnimationClipVector AnimatorOverrideController::GetOriginalClips()const
+{
+ AnimationClipVector clips;
+ if(m_Controller.IsNull())
+ return clips;
+
+ return m_Controller->GetAnimationClips();
+}
+
+// Return the merged clip list that should be used to drive the animator
+// Always start from original clip list and search for each clip if there is a match in m_Clips
+AnimationClipVector AnimatorOverrideController::GetAnimationClips() const
+{
+ AnimationClipVector controllerClips = GetOriginalClips();
+ AnimationClipVector clips;
+ clips.reserve(controllerClips.size());
+
+ for(AnimationClipVector::const_iterator clipIt = controllerClips.begin(); clipIt != controllerClips.end() ; ++clipIt)
+ clips.push_back( FindAnimationClipInMap(*clipIt, return_effective, *clipIt) );
+
+ return clips;
+}
+
+AnimationClipVector AnimatorOverrideController::GetOverrideClips()const
+{
+ AnimationClipVector controllerClips = GetOriginalClips();
+ AnimationClipVector clips;
+ clips.reserve(controllerClips.size());
+
+ for(AnimationClipVector::const_iterator clipIt = controllerClips.begin(); clipIt != controllerClips.end() ; ++clipIt)
+ clips.push_back( FindAnimationClipInMap(*clipIt, return_override, *clipIt) );
+
+ return clips;
+}
+
+mecanim::animation::ControllerConstant* AnimatorOverrideController::GetAsset()
+{
+ if(m_Controller.IsNull())
+ return 0;
+
+ return m_Controller->GetAsset();
+}
+
+void AnimatorOverrideController::BuildAsset()
+{
+ ClearAsset();
+
+ if(m_Controller.IsNull())
+ {
+ m_Clips.clear();
+ return;
+ }
+
+ mecanim::animation::ControllerConstant* controller = m_Controller->GetAsset();
+ if(controller == 0)
+ {
+ m_Clips.clear();
+ return;
+ }
+
+ RegisterAnimationClips();
+ AnimationClipVector clips = GetAnimationClips();
+ m_AnimationSetBindings = UnityEngine::Animation::CreateAnimationSetBindings(controller, clips, m_Allocator);
+}
+
+std::string AnimatorOverrideController::StringFromID(unsigned int ID) const
+{
+ if(m_Controller.IsNull())
+ return "";
+
+ return m_Controller->StringFromID(ID);
+}
+
+void AnimatorOverrideController::ClearAsset()
+{
+ DestroyAnimationSetBindings(m_AnimationSetBindings, m_Allocator);
+ m_AnimationSetBindings = 0;
+
+#if UNITY_EDITOR
+ // This is needed to update AnimatorOverrideController's inspector clip list.
+ // If a user modify the controller by adding or removing a clip we cannot dirty AnimatorOverrideController asset because the user didn't modify it but still we need to update the UI
+ // which is based on the controller clip list.
+ ScriptingInvocation invocation("UnityEngine", "AnimatorOverrideController", "OnInvalidateOverrideController");
+ invocation.AddObject(Scripting::ScriptingWrapperFor(this));
+ invocation.Invoke();
+#endif
+}
+
+template<class TransferFunction>
+void AnimatorOverrideController::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ TRANSFER(m_Controller);
+ TRANSFER(m_Clips);
+}
+
+
+PPtr<RuntimeAnimatorController> AnimatorOverrideController::GetAnimatorController()const
+{
+ return m_Controller;
+}
+
+void AnimatorOverrideController::SetAnimatorController(PPtr<RuntimeAnimatorController> controller)
+{
+ if(m_Controller != controller)
+ {
+ m_AnimationSetNode.Clear();
+
+ m_Controller = controller;
+
+ if(!m_Controller.IsNull())
+ m_Controller->AddObjectUser(m_AnimationSetNode);
+
+ DIRTY_AND_INVALIDATE();
+ }
+}
+
+PPtr<AnimationClip> AnimatorOverrideController::GetClip(std::string const& name, bool returnEffectiveClip)const
+{
+ // if clip 'name' is not an original clip bailout
+ PPtr<AnimationClip> clip = GetOriginalClip(name);
+ if(clip.IsNull())
+ return NULL;
+
+ return returnEffectiveClip ? FindAnimationClipInMap(clip, return_effective) : FindAnimationClipInMap(clip, return_override);
+}
+
+PPtr<AnimationClip> AnimatorOverrideController::GetClip(PPtr<AnimationClip> originalClip, bool returnEffectiveClip)const
+{
+ if(originalClip.IsNull())
+ return NULL;
+
+ return GetClip( originalClip->GetName(), returnEffectiveClip );
+}
+
+void AnimatorOverrideController::SetClip(PPtr<AnimationClip> originalClip, PPtr<AnimationClip> overrideClip)
+{
+ if(originalClip.IsNull())
+ return;
+
+ SetClip(originalClip->GetName(), overrideClip);
+}
+
+void AnimatorOverrideController::SetClip(std::string const& name, PPtr<AnimationClip> clip)
+{
+ // if clip 'name' is not an original clip bailout
+ PPtr<AnimationClip> originalClip = GetOriginalClip(name);
+ if(originalClip.IsNull())
+ return;
+
+ AnimationClipOverrideVector::iterator it = std::find_if(m_Clips.begin(), m_Clips.end(), FindOriginalClip(originalClip) );
+ if(it != m_Clips.end())
+ {
+ it->m_OverrideClip = clip;
+ DIRTY_AND_INVALIDATE();
+ }
+ else
+ {
+ AnimationClipOverride clipOverride;
+ clipOverride.m_OriginalClip = originalClip;
+ clipOverride.m_OverrideClip = clip;
+
+ m_Clips.push_back(clipOverride);
+ DIRTY_AND_INVALIDATE();
+ }
+}
+
+PPtr<AnimationClip> AnimatorOverrideController::GetOriginalClip(std::string const& name)const
+{
+ AnimationClipVector controllerClips = GetOriginalClips();
+ AnimationClipVector::const_iterator it = std::find_if(controllerClips.begin(), controllerClips.end(), FindClip( name.c_str() ) );
+ return it != controllerClips.end() ? *it : NULL;
+}
+
+UnityEngine::Animation::AnimationSetBindings* AnimatorOverrideController::GetAnimationSetBindings()
+{
+ if(m_AnimationSetBindings == 0)
+ BuildAsset();
+
+ return m_AnimationSetBindings;
+}
+
+AnimationClipVector AnimatorOverrideController::GetAnimationClipsToRegister() const
+{
+ return GetOverrideClips();
+}
+
+void AnimatorOverrideController::PerformOverrideClipListCleanup()
+{
+ AnimationClipVector clips = GetOriginalClips();
+
+ AnimationClipOverrideVector clipsToRemove;
+ AnimationClipOverrideVector::iterator it;
+ for(it = m_Clips.begin(); it != m_Clips.end(); ++it)
+ {
+ if(it->m_OriginalClip.IsNull() || it->m_OverrideClip.IsNull())
+ clipsToRemove.push_back(*it);
+ else
+ {
+ AnimationClipVector::const_iterator it2 = std::find_if(clips.begin(), clips.end(), FindClip( it->m_OriginalClip->GetName() ) );
+ if(it2 == clips.end())
+ clipsToRemove.push_back(*it);
+ }
+ }
+
+ for(it = clipsToRemove.begin(); it != clipsToRemove.end(); ++it)
+ {
+ AnimationClipOverrideVector::iterator it2 = std::find_if(m_Clips.begin(), m_Clips.end(), FindOriginalClip( it->m_OriginalClip ) );
+ if(it2 != m_Clips.end())
+ m_Clips.erase(it2);
+ }
+
+ if(clipsToRemove.size() > 0 )
+ DIRTY_AND_INVALIDATE();
+}
+
+
+#undef DIRTY_AND_INVALIDATE
diff --git a/Runtime/Animation/AnimatorOverrideController.h b/Runtime/Animation/AnimatorOverrideController.h
new file mode 100644
index 0000000..48d2905
--- /dev/null
+++ b/Runtime/Animation/AnimatorOverrideController.h
@@ -0,0 +1,97 @@
+#pragma once
+
+
+#include "Runtime/Animation/RuntimeAnimatorController.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/mecanim/memory.h"
+#include "Runtime/Misc/UserList.h"
+#include <vector>
+
+
+class AnimationClip;
+
+typedef std::vector<PPtr<AnimationClip> > AnimationClipVector;
+
+namespace UnityEngine{namespace Animation{struct AnimationSetBindings;}}
+
+struct AnimationClipOverride
+{
+ DEFINE_GET_TYPESTRING(AnimationClipOverride)
+
+ PPtr<AnimationClip> m_OriginalClip;
+ PPtr<AnimationClip> m_OverrideClip;
+
+ template<class TransferFunction>
+ inline void Transfer (TransferFunction& transfer)
+ {
+ TRANSFER(m_OriginalClip);
+ TRANSFER(m_OverrideClip);
+ }
+
+ bool operator== (AnimationClipOverride const& other){return m_OriginalClip == other.m_OriginalClip && m_OverrideClip == other.m_OverrideClip; }
+
+ PPtr<AnimationClip> GetEffectiveClip() const {return !m_OverrideClip.IsNull() ? m_OverrideClip : m_OriginalClip; }
+};
+
+class AnimatorOverrideController : public RuntimeAnimatorController
+{
+public:
+ REGISTER_DERIVED_CLASS (AnimatorOverrideController, RuntimeAnimatorController)
+ DECLARE_OBJECT_SERIALIZE (AnimatorOverrideController)
+
+ static void InitializeClass ();
+ static void CleanupClass () {}
+
+ AnimatorOverrideController (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ virtual mecanim::animation::ControllerConstant* GetAsset();
+ virtual void BuildAsset();
+ virtual void ClearAsset ();
+
+ virtual UnityEngine::Animation::AnimationSetBindings* GetAnimationSetBindings();
+ virtual AnimationClipVector GetAnimationClips()const;
+
+ virtual std::string StringFromID(unsigned int ID) const ;
+
+ PPtr<RuntimeAnimatorController> GetAnimatorController()const;
+ void SetAnimatorController(PPtr<RuntimeAnimatorController> controller);
+
+ AnimationClipVector GetOriginalClips()const;
+ AnimationClipVector GetOverrideClips()const;
+
+ PPtr<AnimationClip> GetClip(std::string const& name, bool returnEffectiveClip)const;
+ void SetClip(std::string const& name, PPtr<AnimationClip> clip);
+ PPtr<AnimationClip> GetClip(PPtr<AnimationClip> originalClip, bool returnEffectiveClip)const;
+ void SetClip(PPtr<AnimationClip> originalClip, PPtr<AnimationClip> overrideClip);
+
+ void PerformOverrideClipListCleanup();
+protected:
+
+ typedef dynamic_array<AnimationClipOverride> AnimationClipOverrideVector;
+
+ PPtr<RuntimeAnimatorController> m_Controller;
+
+ // This list is a map between m_Controller clips and override clips.
+ // We should never rely on this list to return m_Controller clip list because this list may become
+ // offsync when an user edit the controller's clip list(either adding or removing a state).
+ //
+ // The map should only be updated by PerformOverrideClipListCleanup() when user edit the
+ // AnimatorOverrideController in the inspector.
+ AnimationClipOverrideVector m_Clips;
+
+ UnityEngine::Animation::AnimationSetBindings* m_AnimationSetBindings;
+ mecanim::memory::MecanimAllocator m_Allocator;
+ UserListNode m_AnimationSetNode;
+
+private:
+
+ virtual AnimationClipVector GetAnimationClipsToRegister() const;
+
+ PPtr<AnimationClip> GetOriginalClip(std::string const& name)const;
+
+ template<class Functor> PPtr<AnimationClip> FindAnimationClipInMap(PPtr<AnimationClip> const& clip, Functor functor, PPtr<AnimationClip> const& defaultClip = PPtr<AnimationClip>() )const;
+};
+
+
diff --git a/Runtime/Animation/Avatar.cpp b/Runtime/Animation/Avatar.cpp
new file mode 100644
index 0000000..7e62c5d
--- /dev/null
+++ b/Runtime/Animation/Avatar.cpp
@@ -0,0 +1,678 @@
+#include "UnityPrefix.h"
+
+#include "Avatar.h"
+
+
+#include "Runtime/mecanim/human/hand.h"
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+
+#include "MecanimUtility.h"
+
+
+Avatar::Avatar(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode),
+ m_Avatar(0),
+ m_Allocator(1024*4),
+ m_AvatarSize(0),
+ m_ObjectUsers(this)
+{
+
+}
+
+Avatar::~Avatar()
+{
+ NotifyObjectUsers( kDidModifyAvatar );
+}
+
+void Avatar::NotifyObjectUsers(const MessageIdentifier& msg)
+{
+ m_ObjectUsers.SendMessage(msg);
+}
+
+void Avatar::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+ NotifyObjectUsers( kDidModifyAvatar );
+}
+
+
+void Avatar::CheckConsistency ()
+{
+ Super::CheckConsistency();
+ mecanim::animation::AvatarConstant* cst = GetAsset();
+ if(cst != 0)
+ {
+ //@TODO: It looks like CheckConsistency is being abused to generate m_HumanSkeletonIndexArray??? Shouldn't the model importer do this or whatever generates the avatar constant
+
+ // [case 522188] This is an old maya file with avatar already created.
+ // At this time m_HumanSkeletonIndexArray was not there and in this particular case the user did open
+ // the file on a pc without maya so we couldn't reimport the file and create a valid avatar.
+ if(cst->isHuman() && cst->m_HumanSkeletonIndexCount != cst->m_Human->m_Skeleton->m_Count)
+ {
+ cst->m_HumanSkeletonIndexCount = cst->m_Human->m_Skeleton->m_Count;
+
+ // No need to deallocate memory here since we are using the ChainedAllocator.
+ // the allocator will release the memory on next reset().
+ cst->m_HumanSkeletonIndexArray = m_Allocator.ConstructArray<mecanim::int32_t>(cst->m_HumanSkeletonIndexCount);
+
+ mecanim::skeleton::SkeletonBuildIndexArray(cst->m_HumanSkeletonIndexArray.Get(), cst->m_Human->m_Skeleton.Get(), cst->m_AvatarSkeleton.Get());
+ }
+ }
+}
+
+
+IMPLEMENT_OBJECT_SERIALIZE (Avatar)
+IMPLEMENT_CLASS (Avatar)
+
+template<class TransferFunction>
+void Avatar::Transfer (TransferFunction& transfer)
+{
+ SETPROFILERLABEL(AvatarConstant);
+
+ Super::Transfer (transfer);
+
+ TRANSFER(m_AvatarSize);
+
+ if(m_Avatar == 0 )
+ m_Allocator.Reserve(m_AvatarSize);
+
+ transfer.SetUserData(&m_Allocator);
+ TRANSFER_NULLABLE(m_Avatar, mecanim::animation::AvatarConstant);
+ TRANSFER(m_TOS);
+}
+
+void Avatar::SetAsset (mecanim::animation::AvatarConstant* avatarConstant, TOSVector const& tos)
+{
+ // Free previously allocated memory
+ m_Allocator.Reset();
+
+ size_t size = m_AvatarSize;
+ m_Avatar = CopyBlob(*avatarConstant, m_Allocator, size );
+ m_AvatarSize = size;
+ m_TOS = tos;
+
+ NotifyObjectUsers( kDidModifyAvatar );
+}
+
+mecanim::animation::AvatarConstant* Avatar::GetAsset()
+{
+ return m_Avatar;
+}
+
+const mecanim::animation::AvatarConstant* Avatar::GetAsset() const
+{
+ return m_Avatar;
+}
+
+TOSVector const& Avatar::GetTOS() const
+{
+ return m_TOS;
+}
+
+bool Avatar::IsValid()const
+{
+ return GetAsset() != 0;
+}
+
+bool Avatar::IsHuman() const
+{
+ return m_Avatar && m_Avatar->isHuman();
+}
+
+bool Avatar::HasRootMotion() const
+{
+ return m_Avatar && m_Avatar->m_RootMotionBoneIndex != -1;
+}
+
+float Avatar::GetHumanScale() const
+{
+ return IsHuman() ? m_Avatar->m_Human->m_Scale : 1;
+}
+
+float Avatar::GetLeftFeetBottomHeight() const
+{
+ if(!IsHuman())
+ return 0.f;
+
+ return HumanGetFootHeight(m_Avatar->m_Human.Get(), true);
+}
+
+float Avatar::GetRightFeetBottomHeight() const
+{
+ if(!IsHuman())
+ return 0.f;
+
+ return HumanGetFootHeight(m_Avatar->m_Human.Get(), false);
+}
+
+
+void Avatar::SetParameter(int parameterId, float value)
+{
+ mecanim::animation::AvatarConstant* avatar = GetAsset();
+ if(avatar)
+ {
+ switch(parameterId)
+ {
+ case UpperArmTwist: avatar->m_Human->m_ArmTwist = value; break;
+ case LowerArmTwist: avatar->m_Human->m_ForeArmTwist = value; break;
+ case UpperLegTwist: avatar->m_Human->m_UpperLegTwist = value; break;
+ case LowerLegTwsit: avatar->m_Human->m_LegTwist = value; break;
+ case ArmStretch: avatar->m_Human->m_ArmStretch = value; break;
+ case LegStretch: avatar->m_Human->m_LegStretch = value; break;
+ case FeetSpacing: avatar->m_Human->m_FeetSpacing = value; break;
+ default: break;
+ }
+ }
+}
+
+void Avatar::SetMuscleMinMax(int muscleId, float min, float max)
+{
+ int humanId = HumanTrait::BoneFromMuscle(muscleId);
+
+ mecanim::animation::AvatarConstant* avatar = GetAsset();
+
+ int boneId = HumanTrait::GetBoneId(*this, humanId);
+ if(boneId != -1)
+ {
+ int axesId = avatar->m_Human->m_Skeleton->m_Node[boneId].m_AxesId;
+ if(axesId != -1)
+ {
+ math::float4 maxv = avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_Limit.m_Max;
+ math::float4 minv = avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_Limit.m_Min;
+
+ int musclex = HumanTrait::MuscleFromBone(humanId, 0);
+ int muscley = HumanTrait::MuscleFromBone(humanId, 1);
+ int musclez = HumanTrait::MuscleFromBone(humanId, 2);
+
+ if(musclex == muscleId)
+ {
+ minv.x() = math::radians(min);
+ maxv.x() = math::radians(max);
+ }
+ else if(muscley == muscleId)
+ {
+ minv.y() = math::radians(min);
+ maxv.y() = math::radians(max);
+ }
+ else if(musclez == muscleId)
+ {
+ minv.z() = math::radians(min);
+ maxv.z() = math::radians(max);
+ }
+
+ avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_Limit.m_Max = maxv;
+ avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_Limit.m_Min = minv;
+ }
+ }
+}
+
+float Avatar::GetAxisLength(int humanId)const
+{
+ float ret = 0.0f;
+
+ mecanim::animation::AvatarConstant const* avatar = GetAsset();
+
+ int boneId = HumanTrait::GetBoneId(*this, humanId);
+ if(boneId != -1)
+ {
+ int axesId = avatar->m_Human->m_Skeleton->m_Node[boneId].m_AxesId;
+ if(axesId != -1)
+ {
+ ret = avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_Length;
+ }
+ }
+
+ return ret;
+}
+
+Quaternionf Avatar::GetPreRotation(int humanId)const
+{
+ math::float4 ret = math::quatIdentity();
+
+ mecanim::animation::AvatarConstant const* avatar = GetAsset();
+
+ int boneId = HumanTrait::GetBoneId(*this, humanId);
+ if(boneId != -1)
+ {
+ int axesId = avatar->m_Human->m_Skeleton->m_Node[boneId].m_AxesId;
+ if(axesId != -1)
+ {
+ ret = avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_PreQ;
+ }
+ }
+
+ return float4ToQuaternionf(ret);
+}
+Quaternionf Avatar::GetPostRotation(int humanId)const
+{
+ math::float4 ret = math::quatIdentity();
+
+ mecanim::animation::AvatarConstant const* avatar = GetAsset();
+
+ int boneId = HumanTrait::GetBoneId(*this, humanId);
+ if(boneId != -1)
+ {
+ int axesId = avatar->m_Human->m_Skeleton->m_Node[boneId].m_AxesId;
+ if(axesId != -1)
+ {
+ ret = avatar->m_Human->m_Skeleton->m_AxesArray[axesId].m_PostQ;
+ }
+ }
+
+ return float4ToQuaternionf(ret);
+}
+
+Quaternionf Avatar::GetZYPostQ(int index, Quaternionf const& parentQ, Quaternionf const& q)const
+{
+ mecanim::animation::AvatarConstant const* cst = GetAsset();
+
+ math::float4 qzypost = math::quatIdentity();
+
+ int id = HumanTrait::GetBoneId(*this, index);
+ if( id != -1)
+ {
+ int axesId = cst->m_Human->m_Skeleton->m_Node[id].m_AxesId;
+ if(axesId != -1)
+ {
+ math::float4 mpq = QuaternionfTofloat4(parentQ);
+ math::float4 mq = QuaternionfTofloat4(q);
+
+ math::Axes const& axes = cst->m_Human->m_Skeleton->m_AxesArray[axesId];
+
+ math::float4 lq = normalize(quatMul(quatConj(mpq),mq));
+ math::float4 dofzy = ToAxes(axes,lq);
+ dofzy.x() = 0;
+ qzypost = normalize(quatMul(mpq,quatMul( FromAxes(axes,dofzy),axes.m_PostQ)));
+ }
+ }
+ return float4ToQuaternionf(qzypost);
+}
+
+Quaternionf Avatar::GetZYRoll(int index, Vector3f const& v)const
+{
+ mecanim::animation::AvatarConstant const* cst = GetAsset();
+
+ math::float4 qzyroll = math::quatIdentity();
+
+ int id = HumanTrait::GetBoneId(*this, index);
+
+ if( id != -1)
+ {
+ int axesId = cst->m_Human->m_Skeleton->m_Node[id].m_AxesId;
+ if(axesId != -1)
+ {
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4] = {v.x, v.y, v.z, 0};
+
+ math::Axes const& axes = cst->m_Human->m_Skeleton->m_AxesArray[axesId];
+ math::float4 uvw = math::load(buf);
+ qzyroll = ZYRoll2Quat(halfTan(LimitUnproject(axes.m_Limit,uvw))*sgn(axes.m_Sgn));
+ }
+ }
+ return float4ToQuaternionf(qzyroll);
+}
+
+Vector3f Avatar::GetLimitSign(int index)const
+{
+ mecanim::animation::AvatarConstant const* cst = GetAsset();
+ int id = HumanTrait::GetBoneId(*this, index);
+
+ Vector3f sign = Vector3f::one;
+ if( id != -1)
+ {
+ int axesId = cst->m_Human->m_Skeleton->m_Node[id].m_AxesId;
+ if(axesId != -1)
+ {
+ sign.x = cst->m_Human->m_Skeleton->m_AxesArray[axesId].m_Sgn.x().tofloat();
+ sign.y = cst->m_Human->m_Skeleton->m_AxesArray[axesId].m_Sgn.y().tofloat();
+ sign.z = cst->m_Human->m_Skeleton->m_AxesArray[axesId].m_Sgn.z().tofloat();
+ }
+ }
+ return sign;
+}
+
+std::string HumanTrait::GetFingerMuscleName(int index, bool left)
+{
+ std::string fingerName = left ? "Left " : "Right ";
+ if(0 <= index && index < mecanim::hand::s_DoFCount)
+ {
+ int fingerIndex = index / mecanim::hand::kLastFingerDoF;
+ int dofIndex = index % mecanim::hand::kLastFingerDoF;
+
+ fingerName += mecanim::hand::FingerName(fingerIndex);
+ fingerName += " ";
+ fingerName += mecanim::hand::FingerDoFName(dofIndex);
+ }
+ return fingerName;
+}
+
+std::string HumanTrait::GetFingerName(int index, bool left)
+{
+ std::string fingerName = left ? "Left " : "Right ";
+ if(0 <= index && index < mecanim::hand::s_BoneCount)
+ {
+ int fingerIndex = index / mecanim::hand::kLastPhalange;
+ int phalangesIndex = index % mecanim::hand::kLastPhalange;
+
+ fingerName += mecanim::hand::FingerName(fingerIndex);
+ fingerName += " ";
+ fingerName += mecanim::hand::PhalangeName(phalangesIndex);
+ }
+ return fingerName;
+}
+
+int HumanTrait::Body::GetBoneCount()
+{
+ return mecanim::human::kLastBone;
+}
+
+std::string HumanTrait::Body::GetBoneName(int index)
+{
+ return std::string( mecanim::human::BoneName(index) );
+}
+
+int HumanTrait::Body::GetMuscleCount()
+{
+ return mecanim::human::kLastDoF;
+}
+
+std::string HumanTrait::Body::GetMuscleName(int index)
+{
+ return std::string(mecanim::human::MuscleName(index));
+}
+
+int HumanTrait::LeftFinger::GetBoneCount()
+{
+ return mecanim::hand::s_BoneCount;
+}
+
+std::string HumanTrait::LeftFinger::GetBoneName(int index)
+{
+ return HumanTrait::GetFingerName(index, IsLeftHand());
+}
+
+int HumanTrait::LeftFinger::GetMuscleCount()
+{
+ return mecanim::hand::s_DoFCount;
+}
+
+std::string HumanTrait::LeftFinger::GetMuscleName(int index)
+{
+ return HumanTrait::GetFingerMuscleName(index, IsLeftHand());
+}
+
+bool HumanTrait::LeftFinger::IsLeftHand()
+{
+ return true;
+}
+
+
+int HumanTrait::RightFinger::GetBoneCount()
+{
+ return mecanim::hand::s_BoneCount;
+}
+
+std::string HumanTrait::RightFinger::GetBoneName(int index)
+{
+ return HumanTrait::GetFingerName(index, IsLeftHand());
+}
+
+int HumanTrait::RightFinger::GetMuscleCount()
+{
+ return mecanim::hand::s_DoFCount;
+}
+
+std::string HumanTrait::RightFinger::GetMuscleName(int index)
+{
+ return HumanTrait::GetFingerMuscleName(index, IsLeftHand());
+}
+
+bool HumanTrait::RightFinger::IsLeftHand()
+{
+ return false;
+}
+
+std::vector<string> HumanTrait::GetMuscleName()
+{
+ static std::vector<string> muscles = InternalGetMuscleName();
+ return muscles;
+}
+
+std::vector<string> HumanTrait::GetBoneName()
+{
+ static std::vector<string> bones = InternalGetBoneName();
+ return bones;
+}
+
+int HumanTrait::MuscleFromBone(int i, int dofIndex)
+{
+ if(i < LastBone)
+ return mecanim::human::MuscleFromBone(i, dofIndex);
+ else if(i < LastLeftFingerBone)
+ {
+ int muscle = mecanim::hand::MuscleFromBone(i-LastBone, dofIndex);
+ return muscle != -1 ? LastDoF + muscle : -1;
+ }
+ else if(i < LastRightFingerBone)
+ {
+ int muscle = mecanim::hand::MuscleFromBone(i-LastLeftFingerBone, dofIndex);
+ return muscle != -1 ? LastLeftFingerDoF + muscle : -1;
+ }
+ return -1;
+}
+
+int HumanTrait::BoneFromMuscle(int i)
+{
+ if(i < LastDoF)
+ return mecanim::human::BoneFromMuscle(i);
+ else if(i < LastLeftFingerDoF)
+ {
+ int bone = mecanim::hand::BoneFromMuscle(i-LastDoF);
+ return bone != -1 ? LastBone + bone : -1;
+ }
+ else if(i < LastRightFingerDoF)
+ {
+ int bone = mecanim::hand::BoneFromMuscle(i-LastLeftFingerDoF);
+ return bone != -1 ? LastLeftFingerBone + bone : -1;
+ }
+ return -1;
+}
+
+int HumanTrait::GetBoneId(Avatar const& avatar, int humanId)
+{
+ mecanim::animation::AvatarConstant const* cst = avatar.GetAsset();
+
+ int index = -1;
+ if(humanId < LastBone && cst->isHuman())
+ index = cst->m_Human->m_HumanBoneIndex[humanId];
+ else if(humanId < LastLeftFingerBone && cst->isHuman() && !cst->m_Human->m_LeftHand.IsNull())
+ index = cst->m_Human->m_LeftHand->m_HandBoneIndex[humanId - LastBone];
+ else if(humanId < LastRightFingerBone && cst->isHuman() && !cst->m_Human->m_RightHand.IsNull())
+ index = cst->m_Human->m_RightHand->m_HandBoneIndex[humanId - LastLeftFingerBone];
+
+ return index;
+}
+
+bool HumanTrait::RequiredBone(int humanId)
+{
+ if(humanId < LastBone)
+ return mecanim::human::RequiredBone(humanId);
+ return false;
+}
+
+int HumanTrait::RequiredBoneCount()
+{
+ int count = 0;
+ for(int i=0;i<LastBone;i++)
+ {
+ count = RequiredBone(i) ? count + 1 : count;
+ }
+ return count;
+}
+
+bool HumanTrait::HasCollider(Avatar& avatar, int humanId)
+{
+ mecanim::animation::AvatarConstant* cst = avatar.GetAsset();
+
+ bool ret = false;
+ if(humanId < LastBone && cst->isHuman())
+ ret = cst->m_Human->m_ColliderIndex[humanId] != -1;
+ return ret;
+}
+
+int HumanTrait::GetColliderId(Avatar& avatar, int humanId)
+{
+ mecanim::animation::AvatarConstant* cst = avatar.GetAsset();
+
+ int ret = -1;
+ if(humanId < LastBone && cst->isHuman())
+ ret = cst->m_Human->m_ColliderIndex[humanId];
+ return ret;
+}
+
+int HumanTrait::GetParent(int humanId)
+{
+ #define FINGER_INDEX(finger, phalanges) (mecanim::hand::finger * mecanim::hand::kLastPhalange) + mecanim::hand::phalanges
+
+ const int LeftFingerStart = LastBone;
+ const int RightFingerStart = LastLeftFingerBone;
+
+ static int humanParent[] = {
+ -1,
+ mecanim::human::kHips,
+ mecanim::human::kHips,
+ mecanim::human::kLeftUpperLeg,
+ mecanim::human::kRightUpperLeg,
+ mecanim::human::kLeftLowerLeg,
+ mecanim::human::kRightLowerLeg,
+ mecanim::human::kHips,
+ mecanim::human::kSpine,
+ mecanim::human::kChest,
+ mecanim::human::kNeck,
+ mecanim::human::kChest,
+ mecanim::human::kChest,
+ mecanim::human::kLeftShoulder,
+ mecanim::human::kRightShoulder,
+ mecanim::human::kLeftUpperArm,
+ mecanim::human::kRightUpperArm,
+ mecanim::human::kLeftLowerArm,
+ mecanim::human::kRightLowerArm,
+ mecanim::human::kLeftFoot,
+ mecanim::human::kRightFoot,
+ mecanim::human::kHead,
+ mecanim::human::kHead,
+ mecanim::human::kHead,
+ mecanim::human::kLeftHand, LeftFingerStart + FINGER_INDEX(kThumb, kProximal), LeftFingerStart + FINGER_INDEX(kThumb, kIntermediate),
+ mecanim::human::kLeftHand, LeftFingerStart + FINGER_INDEX(kIndex, kProximal), LeftFingerStart + FINGER_INDEX(kIndex, kIntermediate),
+ mecanim::human::kLeftHand, LeftFingerStart + FINGER_INDEX(kMiddle, kProximal), LeftFingerStart + FINGER_INDEX(kMiddle, kIntermediate),
+ mecanim::human::kLeftHand, LeftFingerStart + FINGER_INDEX(kRing, kProximal), LeftFingerStart + FINGER_INDEX(kRing, kIntermediate),
+ mecanim::human::kLeftHand, LeftFingerStart + FINGER_INDEX(kLittle, kProximal), LeftFingerStart + FINGER_INDEX(kLittle, kIntermediate),
+ mecanim::human::kRightHand, RightFingerStart + FINGER_INDEX(kThumb, kProximal), RightFingerStart + FINGER_INDEX(kThumb, kIntermediate),
+ mecanim::human::kRightHand, RightFingerStart + FINGER_INDEX(kIndex, kProximal), RightFingerStart + FINGER_INDEX(kIndex, kIntermediate),
+ mecanim::human::kRightHand, RightFingerStart + FINGER_INDEX(kMiddle, kProximal), RightFingerStart + FINGER_INDEX(kMiddle, kIntermediate),
+ mecanim::human::kRightHand, RightFingerStart + FINGER_INDEX(kRing, kProximal), RightFingerStart + FINGER_INDEX(kRing, kIntermediate),
+ mecanim::human::kRightHand, RightFingerStart + FINGER_INDEX(kLittle, kProximal), RightFingerStart + FINGER_INDEX(kLittle, kIntermediate),
+ };
+
+ Assert(0 <= humanId && humanId < BoneCount);
+ return humanParent[humanId];
+}
+
+float HumanTrait::GetMuscleDefaultMin(int i)
+{
+ int bone = HumanTrait::BoneFromMuscle(i);
+ int dx = HumanTrait::MuscleFromBone (bone, 0);
+ int dy = HumanTrait::MuscleFromBone (bone, 1);
+ int dz = HumanTrait::MuscleFromBone (bone, 2);
+
+ if(i < LastDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::human::GetAxeInfo(bone);
+ if(i == dx) return axeInfo.m_Min[0];
+ else if(i == dy) return axeInfo.m_Min[1];
+ else if(i == dz) return axeInfo.m_Min[2];
+ }
+ else if(i < LastLeftFingerDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::hand::GetAxeInfo(bone-LastBone);
+ if(i == dx) return axeInfo.m_Min[0];
+ else if(i == dy) return axeInfo.m_Min[1];
+ else if(i == dz) return axeInfo.m_Min[2];
+ }
+ else if(i < LastRightFingerDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::hand::GetAxeInfo(bone-LastLeftFingerBone);
+ if(i == dx) return axeInfo.m_Min[0];
+ else if(i == dy) return axeInfo.m_Min[1];
+ else if(i == dz) return axeInfo.m_Min[2];
+ }
+ return 0.f;
+}
+
+float HumanTrait::GetMuscleDefaultMax(int i)
+{
+ int bone = HumanTrait::BoneFromMuscle(i);
+ int dx = HumanTrait::MuscleFromBone (bone, 0);
+ int dy = HumanTrait::MuscleFromBone (bone, 1);
+ int dz = HumanTrait::MuscleFromBone (bone, 2);
+
+ if(i < LastDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::human::GetAxeInfo(bone);
+ if(i == dx) return axeInfo.m_Max[0];
+ else if(i == dy) return axeInfo.m_Max[1];
+ else if(i == dz) return axeInfo.m_Max[2];
+ }
+ else if(i < LastLeftFingerDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::hand::GetAxeInfo(bone-LastBone);
+ if(i == dx) return axeInfo.m_Max[0];
+ else if(i == dy) return axeInfo.m_Max[1];
+ else if(i == dz) return axeInfo.m_Max[2];
+ }
+ else if(i < LastRightFingerDoF)
+ {
+ mecanim::skeleton::SetupAxesInfo const& axeInfo = mecanim::hand::GetAxeInfo(bone-LastLeftFingerBone);
+ if(i == dx) return axeInfo.m_Max[0];
+ else if(i == dy) return axeInfo.m_Max[1];
+ else if(i == dz) return axeInfo.m_Max[2];
+ }
+ return 0.f;
+}
+
+std::vector<string> HumanTrait::InternalGetMuscleName()
+{
+ std::vector<string> muscles;
+
+ muscles.reserve(MuscleCount);
+ for (int i = 0; i < MuscleCount; i++)
+ {
+ if(i < LastDoF)
+ muscles.push_back( Body::GetMuscleName(i) );
+ else if(i < LastLeftFingerDoF)
+ muscles.push_back( LeftFinger::GetMuscleName(i-LastDoF) );
+ else if(i < LastRightFingerDoF)
+ muscles.push_back( RightFinger::GetMuscleName(i-LastLeftFingerDoF) );
+ }
+ return muscles;
+}
+
+std::vector<string> HumanTrait::InternalGetBoneName()
+{
+ std::vector<string> bones;
+
+ bones.reserve(BoneCount);
+ for (int i = 0; i < BoneCount; i++)
+ {
+ if(i < LastBone)
+ bones.push_back( Body::GetBoneName(i) );
+ else if(i < LastLeftFingerBone)
+ bones.push_back( LeftFinger::GetBoneName(i-LastBone) );
+ else if(i < LastRightFingerBone)
+ bones.push_back( RightFinger::GetBoneName(i-LastLeftFingerBone) );
+ }
+
+ return bones;
+}
diff --git a/Runtime/Animation/Avatar.h b/Runtime/Animation/Avatar.h
new file mode 100644
index 0000000..d99dbd0
--- /dev/null
+++ b/Runtime/Animation/Avatar.h
@@ -0,0 +1,149 @@
+#ifndef AVATAR_H
+#define AVATAR_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Misc/UserList.h"
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "Runtime/Serialize/SerializeTraits.h"
+#include "Runtime/mecanim/animation/avatar.h"
+
+#include "Runtime/Animation/MecanimUtility.h"
+
+namespace mecanim { namespace animation { struct AvatarConstant; } }
+
+enum HumanParameter
+{
+ UpperArmTwist = 0,
+ LowerArmTwist,
+ UpperLegTwist,
+ LowerLegTwsit,
+ ArmStretch,
+ LegStretch,
+ FeetSpacing
+};
+
+class Avatar : public NamedObject
+{
+public:
+ REGISTER_DERIVED_CLASS (Avatar, NamedObject)
+ DECLARE_OBJECT_SERIALIZE (Avatar)
+
+ static void InitializeClass (){};
+ static void CleanupClass () {}
+
+ Avatar (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+ virtual void CheckConsistency ();
+
+ void SetAsset (mecanim::animation::AvatarConstant* avatarConstant, TOSVector const& tos);
+
+ mecanim::animation::AvatarConstant* GetAsset();
+ const mecanim::animation::AvatarConstant* GetAsset() const;
+ TOSVector const& GetTOS() const;
+
+ bool IsValid()const;
+
+
+ void SetMuscleMinMax(int muscleId, float min, float max);
+ void SetParameter(int parameterId, float value);
+
+ void NotifyObjectUsers(const MessageIdentifier& msg);
+ void AddObjectUser( UserListNode& node ) { m_ObjectUsers.AddUser(node); }
+
+ bool IsHuman() const;
+ bool HasRootMotion() const;
+ float GetHumanScale() const;
+ float GetLeftFeetBottomHeight() const;
+ float GetRightFeetBottomHeight() const;
+
+ float GetAxisLength(int humanId)const;
+ Quaternionf GetPreRotation(int humanId)const;
+ Quaternionf GetPostRotation(int humanId)const;
+ Quaternionf GetZYPostQ(int index, Quaternionf const& parentQ, Quaternionf const& q)const;
+ Quaternionf GetZYRoll(int index, Vector3f const& v)const;
+ Vector3f GetLimitSign(int index)const;
+
+protected:
+
+ mecanim::memory::ChainedAllocator m_Allocator;
+ mecanim::animation::AvatarConstant* m_Avatar;
+ TOSVector m_TOS;
+
+ UInt32 m_AvatarSize;
+
+ UserList m_ObjectUsers;
+};
+
+class HumanTrait
+{
+public:
+ enum {
+ LastDoF = mecanim::human::kLastDoF,
+ LastLeftFingerDoF = LastDoF + mecanim::hand::s_DoFCount,
+ LastRightFingerDoF = LastLeftFingerDoF + mecanim::hand::s_DoFCount,
+ MuscleCount = LastRightFingerDoF
+ };
+
+ enum {
+ LastBone = mecanim::human::kLastBone,
+ LastLeftFingerBone = LastBone + mecanim::hand::s_BoneCount,
+ LastRightFingerBone = LastLeftFingerBone + mecanim::hand::s_BoneCount,
+ BoneCount = LastRightFingerBone
+ };
+
+ static std::string GetFingerMuscleName(int index, bool left);
+ static std::string GetFingerName(int index, bool left);
+
+ class Body
+ {
+ public:
+ static int GetBoneCount();
+ static std::string GetBoneName(int index);
+ static int GetMuscleCount();
+ static std::string GetMuscleName(int index);
+ };
+
+ class LeftFinger
+ {
+ public:
+ static int GetBoneCount();
+ static std::string GetBoneName(int index);
+ static int GetMuscleCount();
+ static std::string GetMuscleName(int index);
+ static bool IsLeftHand();
+ };
+
+ class RightFinger
+ {
+ public:
+ static int GetBoneCount();
+ static std::string GetBoneName(int index);
+ static int GetMuscleCount();
+ static std::string GetMuscleName(int index);
+ static bool IsLeftHand();
+ };
+
+ static std::vector<string> GetMuscleName();
+ static std::vector<string> GetBoneName();
+ static int MuscleFromBone(int i, int dofIndex);
+ static int BoneFromMuscle(int i);
+ static int GetBoneId(Avatar const& avatar, int humanId);
+ static bool RequiredBone(int humanId);
+ static int RequiredBoneCount();
+ static bool HasCollider(Avatar& avatar, int humanId);
+ static int GetColliderId(Avatar& avatar, int humanId);
+
+ static int GetParent(int humanId);
+
+ static float GetMuscleDefaultMin(int i);
+ static float GetMuscleDefaultMax(int i);
+
+protected:
+ static std::vector<string> InternalGetMuscleName();
+ static std::vector<string> InternalGetBoneName();
+};
+
+
+#endif
+
diff --git a/Runtime/Animation/AvatarBuilder.cpp b/Runtime/Animation/AvatarBuilder.cpp
new file mode 100644
index 0000000..abfbfac
--- /dev/null
+++ b/Runtime/Animation/AvatarBuilder.cpp
@@ -0,0 +1,807 @@
+#include "UnityPrefix.h"
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+
+#include "Runtime/Profiler/Profiler.h"
+
+#include "AvatarBuilder.h"
+#include "Avatar.h"
+
+
+
+namespace
+{
+ int Find( AvatarBuilder::NamedTransforms const& transforms, Transform* parent )
+ {
+ for(int i=0;i<transforms.size();i++)
+ if(transforms[i].transform == parent) return i;
+ return -1;
+ }
+
+ template<typename TYPE>
+ int GetIndexArray(HumanDescription const& humanDescription, AvatarBuilder::NamedTransforms const& namedTransform, std::vector<int> &indexArray)
+ {
+ int ret = 0;
+
+ for(int i = 0 ; i < TYPE::GetBoneCount(); ++i)
+ {
+ HumanBoneList::const_iterator it = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(TYPE::GetBoneName(i)) );
+
+ if(it != humanDescription.m_Human.end())
+ {
+ for(int j = 0; j < namedTransform.size(); j++)
+ {
+ if((*it).m_BoneName == namedTransform[j].name)
+ {
+ indexArray[j] = i;
+ ret++;
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+
+ mecanim::skeleton::Skeleton* BuildSkeleton(const AvatarBuilder::NamedTransforms & transform, TOSVector& tos, mecanim::memory::Allocator& alloc)
+ {
+ mecanim::skeleton::Skeleton * skel = mecanim::skeleton::CreateSkeleton(transform.size(), 0, alloc);
+
+ mecanim::uint32_t i;
+ for(i=0;i<skel->m_Count;i++)
+ {
+ // Find return -1 if transform is not found exactly like our data representation
+ // when node doesn't have any parent
+ skel->m_Node[i].m_ParentId = Find( transform, transform[i].transform->GetParent() );
+ skel->m_Node[i].m_AxesId = -1;
+ skel->m_ID[i] = ProccessString(tos, transform[i].path);
+ }
+ return skel;
+ }
+
+ void MarkBoneUp(mecanim::skeleton::Skeleton *avatarSkeleton, std::vector<bool> &isMark, int index, int stopIndex)
+ {
+ isMark[index] = true;
+
+ if(index != stopIndex)
+ {
+ MarkBoneUp(avatarSkeleton,isMark,avatarSkeleton->m_Node[index].m_ParentId,stopIndex);
+ }
+ }
+
+ mecanim::skeleton::Skeleton* BuildHumanSkeleton(mecanim::skeleton::Skeleton *avatarSkeleton, std::vector<int> humanBoneIndexArray, std::vector<int> leftHandIndexArray, std::vector<int> rightHandIndexArray, mecanim::memory::Allocator& alloc)
+ {
+ int humanCount = 0;
+ int leftHandCount = 0;
+ int rightHandCount = 0;
+ int hipsIndex = -1;
+
+ for(int i = 0; i < avatarSkeleton->m_Count; i++)
+ {
+ humanCount += humanBoneIndexArray[i] != -1 ? 1 : 0;
+ leftHandCount += leftHandIndexArray[i] != -1 ? 1 : 0;
+ rightHandCount += rightHandIndexArray[i] != -1 ? 1 : 0;
+ hipsIndex = humanBoneIndexArray[i] == mecanim::human::kHips ? i : hipsIndex;
+ }
+
+ std::vector<bool> isHumanBone(avatarSkeleton->m_Count,false);
+
+ for(int i = 0; i < avatarSkeleton->m_Count; i++)
+ {
+ if(humanBoneIndexArray[i] != -1)
+ {
+ MarkBoneUp(avatarSkeleton,isHumanBone,i,hipsIndex);
+ }
+
+ if(leftHandIndexArray[i] != -1)
+ {
+ MarkBoneUp(avatarSkeleton,isHumanBone,i,hipsIndex);
+ }
+
+ if(rightHandIndexArray[i] != -1)
+ {
+ MarkBoneUp(avatarSkeleton,isHumanBone,i,hipsIndex);
+ }
+ }
+
+ int boneCount = 0;
+
+ for(int i = 0; i < isHumanBone.size(); i++)
+ {
+ boneCount += isHumanBone[i] ? 1 : 0;
+ }
+
+ mecanim::skeleton::Skeleton *skel = mecanim::skeleton::CreateSkeleton(1+boneCount,humanCount+leftHandCount+rightHandCount,alloc);
+
+ int nodeIndex = 0;
+ int axesIndex = 0;
+
+ skel->m_ID[nodeIndex] = avatarSkeleton->m_ID[avatarSkeleton->m_Node[hipsIndex].m_ParentId];
+ skel->m_Node[nodeIndex].m_ParentId = -1;
+ skel->m_Node[nodeIndex].m_AxesId = -1;
+
+ nodeIndex++;
+
+ for(int i = 0; i < avatarSkeleton->m_Count; i++)
+ {
+ if(isHumanBone[i])
+ {
+ skel->m_ID[nodeIndex] = avatarSkeleton->m_ID[i];
+ skel->m_Node[nodeIndex].m_ParentId = mecanim::skeleton::SkeletonFindNode(skel,avatarSkeleton->m_ID[avatarSkeleton->m_Node[i].m_ParentId]);
+ skel->m_Node[nodeIndex].m_AxesId = humanBoneIndexArray[i] != -1 || leftHandIndexArray[i] != -1 || rightHandIndexArray[i] != -1 ? axesIndex++ : -1;
+
+ nodeIndex++;
+ }
+ }
+
+ return skel;
+ }
+
+ mecanim::skeleton::Skeleton* BuildRootMotionSkeleton(mecanim::skeleton::Skeleton *avatarSkeleton, int rootMotionIndex, mecanim::memory::Allocator& alloc)
+ {
+
+ std::vector<bool> isRootMotionBone(avatarSkeleton->m_Count,false);
+
+ MarkBoneUp(avatarSkeleton,isRootMotionBone,rootMotionIndex,0);
+
+ int rootMotionCount = 0;
+
+ for(int i = 0; i < isRootMotionBone.size(); i++)
+ {
+ rootMotionCount += isRootMotionBone[i] ? 1 : 0;
+ }
+
+ mecanim::skeleton::Skeleton *skel = mecanim::skeleton::CreateSkeleton(rootMotionCount,0,alloc);
+
+ int nodeIndex = 0;
+
+ for(int i = 0; i < avatarSkeleton->m_Count; i++)
+ {
+ if(isRootMotionBone[i])
+ {
+ skel->m_ID[nodeIndex] = avatarSkeleton->m_ID[i];
+ skel->m_Node[nodeIndex].m_ParentId = nodeIndex-1;
+
+ nodeIndex++;
+ }
+ }
+
+ return skel;
+ }
+
+ void SetAxes(mecanim::human::Human* human, SkeletonBoneLimit const& skeletonBoneLimit, int id)
+ {
+ if(id != -1 && skeletonBoneLimit.m_Modified)
+ {
+ int axesId = human->m_Skeleton->m_Node[id].m_AxesId;
+ if(axesId != -1)
+ {
+ math::Axes& axes = human->m_Skeleton->m_AxesArray[axesId];
+
+ math::float4 minValue = math::radians(math::float4(skeletonBoneLimit.m_Min[0], skeletonBoneLimit.m_Min[1], skeletonBoneLimit.m_Min[2], 0.f));
+ math::float4 maxValue = math::radians(math::float4(skeletonBoneLimit.m_Max[0], skeletonBoneLimit.m_Max[1], skeletonBoneLimit.m_Max[2], 0.f));
+ axes.m_Limit.m_Min = minValue;
+ axes.m_Limit.m_Max = maxValue;
+ }
+ }
+ }
+
+ class SetupAxesHelper
+ {
+ public:
+ SetupAxesHelper(mecanim::human::Human* human, bool exist, mecanim::int32_t* index): mHuman(human),mExist(exist),mIndex(index){}
+
+ void operator()(HumanBone const& humanBone, int i)
+ {
+ int index = mExist ? mIndex[i] : -1;
+ SetAxes(mHuman, humanBone.m_Limit, index);
+ }
+ protected:
+ mecanim::human::Human* mHuman;
+ mecanim::skeleton::SkeletonPose* mPose;
+ bool mExist;
+ mecanim::int32_t* mIndex;
+ };
+
+ template<class Trait, class List, class Function> void for_each(List const& list, Function f)
+ {
+ for (int i = 0; i < Trait::GetBoneCount(); ++i )
+ {
+ typename List::const_iterator it = std::find_if(list.begin(), list.end(), FindHumanBone(Trait::GetBoneName(i)) );
+ if(it != list.end())
+ {
+ f(*it, i);
+ }
+ }
+ }
+
+ void SetupAxes(mecanim::human::Human* human, HumanDescription const& humanDescription)
+ {
+ for_each<HumanTrait::Body>(humanDescription.m_Human, SetupAxesHelper(human, true, human->m_HumanBoneIndex));
+
+ for_each<HumanTrait::LeftFinger>(humanDescription.m_Human, SetupAxesHelper(human, human->m_HasLeftHand, human->m_HasLeftHand ? human->m_LeftHand->m_HandBoneIndex : 0));
+
+ for_each<HumanTrait::RightFinger>(humanDescription.m_Human, SetupAxesHelper(human, human->m_HasRightHand, human->m_HasRightHand ? human->m_RightHand->m_HandBoneIndex : 0));
+ }
+
+ struct IndexFromBoneName
+ {
+ UnityStr m_Predicate;
+ IndexFromBoneName(const UnityStr& predicate):m_Predicate(predicate){}
+
+ bool operator()(AvatarBuilder::NamedTransform const& namedTransform){return namedTransform.name == m_Predicate;}
+ };
+
+ static int GetIndexFromBoneName (AvatarBuilder::NamedTransforms::const_iterator start, AvatarBuilder::NamedTransforms::const_iterator stop, const UnityStr& boneName)
+ {
+ AvatarBuilder::NamedTransforms::const_iterator it = std::find_if(start, stop, IndexFromBoneName(boneName) );
+ if(it!=stop)
+ return it - start;
+
+ return -1;
+ }
+
+ static void OverwriteTransforms(mecanim::skeleton::Skeleton* skeleton, mecanim::skeleton::SkeletonPose* pose, const HumanDescription& humanDescription, const AvatarBuilder::NamedTransforms& namedTransforms, bool force)
+ {
+ if( humanDescription.m_Skeleton.size() > 0)
+ {
+ const SkeletonBone& skeletonBone = humanDescription.m_Skeleton[0];
+ if(skeletonBone.m_TransformModified || force)
+ {
+ if(namedTransforms[0].name == skeletonBone.m_Name)
+ {
+ pose->m_X[0] = xformFromUnity(skeletonBone.m_Position,skeletonBone.m_Rotation,skeletonBone.m_Scale);
+ }
+ }
+
+ for (int i = 1; i < humanDescription.m_Skeleton.size(); ++i )
+ {
+ SkeletonBone const& skeletonBone = humanDescription.m_Skeleton[i];
+ if(skeletonBone.m_TransformModified || force)
+ {
+ int skeletonIndex = GetIndexFromBoneName (namedTransforms.begin()+1, namedTransforms.end(), skeletonBone.m_Name) + 1;
+ if (skeletonIndex != 0)
+ {
+ pose->m_X[skeletonIndex] = xformFromUnity(skeletonBone.m_Position,skeletonBone.m_Rotation,skeletonBone.m_Scale);
+ }
+ }
+ }
+ }
+ }
+}
+
+HumanBone::HumanBone():
+m_HumanName(""),
+m_BoneName("")
+//m_ColliderPosition(Vector3f::zero),
+//m_ColliderRotation(Quaternionf::identity()),
+//m_ColliderScale(Vector3f::one)
+{
+}
+
+HumanBone::HumanBone(std::string const& humanName):
+m_HumanName(humanName),
+m_BoneName("")
+//m_ColliderPosition(Vector3f::zero),
+//m_ColliderRotation(Quaternionf::identity()),
+//m_ColliderScale(Vector3f::one)
+
+{
+}
+
+class FindBone
+{
+protected:
+ UnityStr mName;
+public:
+ FindBone(const UnityStr& name):mName(name){}
+ bool operator() (const AvatarBuilder::NamedTransform & bone){ return mName == bone.name; }
+};
+
+class FindBonePath
+{
+protected:
+ UnityStr mName;
+public:
+ FindBonePath(const UnityStr& name):mName(name){}
+
+ bool operator() (const AvatarBuilder::NamedTransform & bone){ return mName == bone.path; }
+};
+
+PROFILER_INFORMATION (gAvatarBuilderBuildAvatar, "AvatarBuilder.BuildAvatar", kProfilerAnimation);
+
+std::string AvatarBuilder::BuildAvatar(Avatar& avatar, const Unity::GameObject& go, bool doOptimizeTransformHierarchy, const HumanDescription& humanDescription, Options options)
+{
+ PROFILER_AUTO(gAvatarBuilderBuildAvatar, NULL)
+
+ NamedTransforms namedTransform;
+
+ std::string error = AvatarBuilder::GenerateAvatarMap(go, namedTransform, humanDescription, doOptimizeTransformHierarchy, options.avatarType, options.useMask);
+ if(!error.empty())
+ return Format("AvatarBuilder '%s': %s", go.GetName(), error.c_str());
+
+ mecanim::memory::ChainedAllocator alloc(30*1024);
+
+ TOSVector tos;
+
+ // build avatar skeleton
+ mecanim::skeleton::Skeleton* avatarSK = BuildSkeleton(namedTransform, tos, alloc);
+ mecanim::skeleton::SkeletonPose* avatarPose = mecanim::skeleton::CreateSkeletonPose(avatarSK, alloc);
+ mecanim::skeleton::SkeletonPose* avatarGPose = mecanim::skeleton::CreateSkeletonPose(avatarSK, alloc);
+ mecanim::uint32_t * nameIDArray = alloc.ConstructArray<mecanim::uint32_t>(namedTransform.size());
+
+ for(int i = 0; i < namedTransform.size(); i++)
+ {
+ nameIDArray[i] = mecanim::processCRC32(GetLastPathNameComponent(namedTransform[i].path.c_str(), namedTransform[i].path.size()));
+ }
+
+ if(options.readTransform)
+ ReadFromLocalTransformToSkeletonPose(avatarPose, namedTransform);
+
+ // Overwrite transform that has been set by the user
+ OverwriteTransforms(avatarSK, avatarPose, humanDescription, namedTransform, true);
+
+ mecanim::skeleton::SkeletonPoseComputeGlobal(avatarSK, avatarPose, avatarGPose);
+
+ // Fill avatarDefaultPose. This pose will be used in optimized mode, when the character is not animated.
+ // Please note that, although this value is only used in optimized mode, we initialize it here all the time.
+ // Because, optimization/de-optimization should can be applied on the fly.
+ // We don't want to trigger re-import (rebuild avatar).
+ mecanim::skeleton::SkeletonPose* avatarDefaultPose = mecanim::skeleton::CreateSkeletonPose(avatarSK, alloc);
+ ReadFromLocalTransformToSkeletonPose(avatarDefaultPose, namedTransform);
+
+ mecanim::human::Human* human = 0;
+ mecanim::skeleton::Skeleton* humanSK = 0;
+ mecanim::skeleton::SkeletonPose* humanPose = 0;
+ mecanim::skeleton::SkeletonPose* humanGPose = 0;
+
+ int rootMotionIndex = -1;
+ math::xform rootMotionX;
+ mecanim::skeleton::Skeleton *rootMotionSK = 0;
+
+ if(options.avatarType == kHumanoid)
+ {
+ // build human skeleton
+ std::vector<int> humanIndexArray(avatarSK->m_Count,-1);
+ std::vector<int> leftHandIndexArray(avatarSK->m_Count,-1);
+ std::vector<int> rightHandIndexArray(avatarSK->m_Count,-1);
+
+ GetIndexArray<HumanTrait::Body>(humanDescription,namedTransform,humanIndexArray);
+ bool leftHandValid = GetIndexArray<HumanTrait::LeftFinger>(humanDescription,namedTransform,leftHandIndexArray) > 0;
+ bool rightHandValid = GetIndexArray<HumanTrait::RightFinger>(humanDescription,namedTransform,rightHandIndexArray) > 0;
+
+ humanSK = BuildHumanSkeleton(avatarSK, humanIndexArray, leftHandIndexArray, rightHandIndexArray,alloc);
+ humanPose = mecanim::skeleton::CreateSkeletonPose(humanSK, alloc);
+ humanGPose = mecanim::skeleton::CreateSkeletonPose(humanSK, alloc);
+
+ // build human. Setup human with 'pose', which will be initialized when HumanSetupAxes will be call
+
+ //@TODO: SHould we remove support for handles in the runtime too?
+ human = mecanim::human::CreateHuman(humanSK, humanPose, 0, humanSK->m_AxesCount, alloc);
+ mecanim::hand::Hand* leftHand = leftHandValid ? mecanim::hand::CreateHand(alloc) : 0;
+ mecanim::hand::Hand* rightHand = rightHandValid ? mecanim::hand::CreateHand(alloc) : 0;
+ human->m_LeftHand = leftHand;
+ human->m_HasLeftHand = leftHandValid;
+ human->m_RightHand = rightHand;
+ human->m_HasRightHand = rightHandValid;
+
+ for(int i = 0; i < avatarSK->m_Count; i++)
+ {
+ if(humanIndexArray[i] != -1)
+ human->m_HumanBoneIndex[humanIndexArray[i]] = mecanim::skeleton::SkeletonFindNode(humanSK,avatarSK->m_ID[i]);
+
+ if(leftHandValid && leftHandIndexArray[i] != -1)
+ leftHand->m_HandBoneIndex[leftHandIndexArray[i]] = mecanim::skeleton::SkeletonFindNode(humanSK,avatarSK->m_ID[i]);
+
+ if(rightHandValid && rightHandIndexArray[i] != -1)
+ rightHand->m_HandBoneIndex[rightHandIndexArray[i]] = mecanim::skeleton::SkeletonFindNode(humanSK,avatarSK->m_ID[i]);
+ }
+
+ human->m_ArmTwist = humanDescription.m_ArmTwist;
+ human->m_ForeArmTwist = humanDescription.m_ForeArmTwist;
+ human->m_UpperLegTwist = humanDescription.m_UpperLegTwist;
+ human->m_LegTwist = humanDescription.m_LegTwist;
+ human->m_ArmStretch = humanDescription.m_ArmStretch;
+ human->m_LegStretch = humanDescription.m_LegStretch;
+ human->m_FeetSpacing = humanDescription.m_FeetSpacing;
+
+ mecanim::skeleton::SkeletonPoseCopy(avatarSK,avatarGPose,humanSK,humanGPose);
+ humanGPose->m_X[0] = math::xformIdentity();
+
+ mecanim::human::HumanAdjustMass(human);
+ mecanim::human::HumanSetupAxes(human, humanGPose);
+ mecanim::human::HumanSetupCollider(human, humanGPose);
+
+ if(leftHandValid) mecanim::hand::HandSetupAxes(leftHand, humanGPose, humanSK, true);
+ if(rightHandValid) mecanim::hand::HandSetupAxes(rightHand, humanGPose, humanSK, false);
+
+ SetupAxes(human, humanDescription);
+ }
+ else
+ {
+ rootMotionIndex = GetIndexFromBoneName (namedTransform.begin(), namedTransform.end(), humanDescription.m_RootMotionBoneName);
+
+ if(rootMotionIndex != -1)
+ {
+ rootMotionX = avatarGPose->m_X[rootMotionIndex];
+ rootMotionSK = BuildRootMotionSkeleton(avatarSK,rootMotionIndex,alloc);
+ }
+ }
+
+ mecanim::animation::AvatarConstant* avatarConstant = mecanim::animation::CreateAvatarConstant( avatarSK, avatarPose, avatarDefaultPose, human, rootMotionSK, rootMotionIndex, rootMotionX, alloc);
+ avatarConstant->m_SkeletonNameIDCount = namedTransform.size();
+ avatarConstant->m_SkeletonNameIDArray = nameIDArray;
+ avatar.SetAsset(avatarConstant, tos);
+ return std::string();
+}
+
+Transform* AvatarBuilder::GetTransform(int id, HumanDescription const& humanDescription, NamedTransforms const& namedTransform, std::vector<string> const& boneName)
+{
+ HumanBoneList::const_iterator it1 = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(boneName[id]) );
+ if(it1!=humanDescription.m_Human.end())
+ {
+ NamedTransforms::const_iterator it2 = std::find_if(namedTransform.begin(), namedTransform.end(), FindBone( (*it1).m_BoneName ) );
+ if(it2 != namedTransform.end())
+ {
+ return it2->transform;
+ }
+ }
+ return 0;
+}
+
+bool AvatarBuilder::IsValidHuman(HumanDescription const& humanDescription, NamedTransforms const& namedTransform, std::string& error)
+{
+ int i;
+ for(i = 0 ; i < HumanTrait::Body::GetBoneCount(); ++i)
+ {
+ if(HumanTrait::RequiredBone(i))
+ {
+ HumanBoneList::const_iterator it1 = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(HumanTrait::Body::GetBoneName(i)) );
+ if(it1!=humanDescription.m_Human.end())
+ {
+ NamedTransforms::const_iterator it2 = std::find_if(namedTransform.begin(), namedTransform.end(), FindBone( (*it1).m_BoneName ) );
+ if(it2 == namedTransform.end())
+ {
+ error = Format("Transform '%s' for human bone '%s' not found", (*it1).m_BoneName.c_str(), HumanTrait::Body::GetBoneName(i).c_str() );
+ return false;
+ }
+ }
+ else
+ {
+ error = Format("Required human bone '%s' not found", HumanTrait::Body::GetBoneName(i).c_str() );
+ return false;
+ }
+ }
+ }
+
+ // Look if all the bone hierarchy parenting is valid
+ std::vector<string> boneName = HumanTrait::GetBoneName();
+ Transform* hips = GetTransform(0, humanDescription, namedTransform, boneName);
+ if(hips && !hips->GetParent())
+ {
+ error = Format("Hips bone '%s' must have a parent", hips->GetName() );
+ return false;
+ }
+ else if(hips && hips->GetParent())
+ {
+ if(std::find_if(namedTransform.begin(), namedTransform.end(), FindBone( hips->GetParent()->GetName() )) == namedTransform.end())
+ {
+ error = Format("Hips bone parent '%s' must be included in the HumanDescription Skeleton", hips->GetParent()->GetName());
+ return false;
+ }
+ }
+
+ for(i = 0 ; i < HumanTrait::BoneCount; ++i)
+ {
+ Transform* child = GetTransform(i, humanDescription, namedTransform, boneName);
+ if(child)
+ {
+ // find out next required parent bone
+ int parentId = HumanTrait::GetParent(i);
+ while(parentId != -1 && !HumanTrait::RequiredBone(parentId))
+ parentId = HumanTrait::GetParent(parentId);
+
+ if(parentId != -1)
+ {
+ Transform* parent = GetTransform(parentId, humanDescription, namedTransform, boneName);
+ if(!IsChildOrSameTransform(*child, *parent))
+ {
+ error = Format("Transform '%s' is not an ancestor of '%s'", parent->GetName(), child->GetName() );
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool AvatarBuilder::IsValidHumanDescription(HumanDescription const& humanDescription, std::string& error)
+{
+ int i ;
+ for(i = 0 ; i < HumanTrait::Body::GetBoneCount(); ++i)
+ {
+ if(HumanTrait::RequiredBone(i))
+ {
+ HumanBoneList::const_iterator it1 = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(HumanTrait::Body::GetBoneName(i)) );
+ if(it1 == humanDescription.m_Human.end())
+ {
+ error = Format("Required human bone '%s' not found", HumanTrait::Body::GetBoneName(i).c_str() );
+ return false;
+ }
+ }
+ }
+
+ for(i = 0 ; i < humanDescription.m_Human.size() ; i++)
+ {
+ if(!humanDescription.m_Human[i].m_BoneName.empty())
+ {
+ HumanBoneList::const_iterator foundDuplicated = std::find_if(humanDescription.m_Human.begin() + i +1, humanDescription.m_Human.end(), FindHumanBone(humanDescription.m_Human[i].m_HumanName) );
+ if(foundDuplicated != humanDescription.m_Human.end())
+ {
+ error = Format("Found duplicate human bone '%s' with transform '%s' and '%s'", humanDescription.m_Human[i].m_HumanName.c_str(), foundDuplicated->m_BoneName.c_str(), humanDescription.m_Human[i].m_BoneName.c_str() );
+ return false;
+ }
+ }
+ }
+
+
+ for(i = 0 ; i < humanDescription.m_Human.size() ; i++)
+ {
+ if(!humanDescription.m_Human[i].m_BoneName.empty())
+ {
+ HumanBoneList::const_iterator foundDuplicated = std::find_if(humanDescription.m_Human.begin() + i +1, humanDescription.m_Human.end(), FindBoneName(humanDescription.m_Human[i].m_BoneName) );
+ if(foundDuplicated != humanDescription.m_Human.end())
+ {
+ error = Format("Found duplicate transform '%s' for human bone '%s' and '%s'", humanDescription.m_Human[i].m_BoneName.c_str(), foundDuplicated->m_HumanName.c_str(), humanDescription.m_Human[i].m_HumanName.c_str() );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+std::string AvatarBuilder::GenerateAvatarMap(GameObject const& go, NamedTransforms& namedTransform, const HumanDescription& humanDescription, bool doOptimizeTransformHierarchy, AvatarType avatarType, bool useMask)
+{
+ Assert(avatarType == kHumanoid || avatarType == kGeneric);
+
+ std::string error;
+ if (avatarType == kHumanoid)
+ {
+ if (!AvatarBuilder::IsValidHumanDescription(humanDescription, error) )
+ return error;
+ }
+
+ Transform& rootTransform = go.GetComponent(Transform);
+
+ // Get all the transform below root transform
+ NamedTransforms namedAllTransform;
+ GetAllChildren(rootTransform, namedAllTransform);
+
+ Transform* root = &rootTransform;
+ if(avatarType == kHumanoid)
+ {
+ root = GetHipsNode(humanDescription, namedAllTransform);
+ if(root == 0)
+ {
+ HumanBoneList::const_iterator it = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(HumanTrait::Body::GetBoneName(mecanim::human::kHips)) );
+
+ return Format("Transform '%s' for human bone '%s' not found", it->m_BoneName.c_str(), HumanTrait::Body::GetBoneName(mecanim::human::kHips).c_str() );
+ }
+ }
+ else if(avatarType == kGeneric && !humanDescription.m_RootMotionBoneName.empty())
+ {
+ root = GetRootMotionNode(humanDescription, namedAllTransform);
+ if(root == 0)
+ return Format("Cannot find root motion transform '%s'", humanDescription.m_RootMotionBoneName.c_str() );
+ }
+
+
+ // Mask is mainly used for API call, when user want to select only a sub part of a hierarchy
+ std::vector<UnityStr> mask;
+ if(useMask)
+ {
+ SkeletonBoneList::const_iterator it;
+ for(it = humanDescription.m_Skeleton.begin(); it != humanDescription.m_Skeleton.end(); ++it)
+ mask.push_back(UnityStr(it->m_Name.c_str()));
+ }
+
+ GetAllChildren(rootTransform, namedTransform, mask);
+
+ if(avatarType == kHumanoid)
+ {
+ if (!AvatarBuilder::IsValidHuman(humanDescription, namedTransform, error))
+ return error;
+ }
+
+ return std::string();
+}
+
+template <class InputIterator1> bool Include(InputIterator1 first1, InputIterator1 last1, UnityStr const& e)
+{
+ while(first1!=last1)
+ {
+ if( (*first1) == e) return true;
+ first1++;
+ }
+ return false;
+}
+
+void AvatarBuilder::GetAllChildren (Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask)
+{
+ UnityStr path = CalculateTransformPath (node, &node.GetRoot());
+ GetAllChildren (node, path, transforms, mask);
+}
+
+void AvatarBuilder::GetAllChildren (Transform& node, UnityStr& path, NamedTransforms& transforms, std::vector<UnityStr> const& mask)
+{
+ // @TODO: Does it make sense that you can exclude a node but it's child can still be included?
+ // That seems like it can only break stuff...
+ bool isIncluded = mask.size() == 0 || Include(mask.begin(), mask.end(), UnityStr(node.GetName()));
+ if(isIncluded)
+ {
+ transforms.push_back(NamedTransform());
+ transforms.back().transform = &node;
+ transforms.back().path = path;
+ transforms.back().name = node.GetName();
+ }
+
+ for (int i=0;i<node.GetChildrenCount();i++)
+ {
+ Transform& child = node.GetChild(i);
+ size_t pathLength = path.size();
+ AppendTransformPath (path, child.GetName());
+
+ GetAllChildren(child, path, transforms, mask);
+
+ path.resize(pathLength);
+ }
+}
+
+void AvatarBuilder::GetAllParent (Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask, bool includeSelf)
+{
+ GetAllParent (node.GetRoot(), node, transforms, mask, includeSelf);
+}
+
+void AvatarBuilder::GetAllParent (Transform& root, Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask, bool includeSelf)
+{
+ if(node.GetParent() != NULL)
+ {
+ Transform& parent = *node.GetParent();
+
+ // Insertion order matter, top most node must be inserted first
+ GetAllParent(root, parent, transforms, mask);
+
+ bool isIncluded = mask.size() == 0 || Include(mask.begin(), mask.end(), UnityStr(parent.GetName()));
+ if(isIncluded)
+ {
+ transforms.push_back(NamedTransform());
+ transforms.back().transform = &parent;
+ transforms.back().path = CalculateTransformPath(parent, &root);
+ transforms.back().name = parent.GetName();
+ }
+ }
+
+ if(includeSelf)
+ {
+ bool isIncluded = mask.size() == 0 || Include(mask.begin(), mask.end(), UnityStr(node.GetName()));
+ if(isIncluded)
+ {
+ transforms.push_back(NamedTransform());
+ transforms.back().transform = &node;
+ transforms.back().path = CalculateTransformPath(node, &root);
+ transforms.back().name = node.GetName();
+ }
+ }
+}
+
+Transform* AvatarBuilder::GetHipsNode(const HumanDescription& humanDescription, NamedTransforms const& transforms)
+{
+ HumanBoneList::const_iterator it = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindHumanBone(HumanTrait::Body::GetBoneName(mecanim::human::kHips)) );
+ if(it != humanDescription.m_Human.end())
+ {
+ NamedTransforms::const_iterator it2 = std::find_if(transforms.begin(), transforms.end(), FindBone( it->m_BoneName ) );
+ if(it2 != transforms.end())
+ return it2->transform;
+ }
+ return 0;
+}
+
+Transform* AvatarBuilder::GetRootMotionNode(const HumanDescription& humanDescription, NamedTransforms const& transforms)
+{
+ NamedTransforms::const_iterator it = std::find_if(transforms.begin(), transforms.end(), FindBone( humanDescription.m_RootMotionBoneName ) );
+ return it != transforms.end() ? it->transform : 0;
+}
+
+bool AvatarBuilder::RemoveAllNoneHumanLeaf(NamedTransforms& namedTransform, HumanDescription const & humanDescription)
+{
+ bool didRemove = false;
+
+ for (int n = 0; n < namedTransform.size(); n++)
+ {
+ Transform& transfom = *namedTransform[n].transform;
+ bool hasAnyChildMapped = false;
+ for(int i=0;i < transfom.GetChildrenCount() && !hasAnyChildMapped; i++)
+ {
+ NamedTransforms::const_iterator it2 = std::find_if(namedTransform.begin(), namedTransform.end(), FindBone( transfom.GetChild(i).GetName() ) );
+ hasAnyChildMapped = it2 != namedTransform.end();
+ }
+
+ if( !hasAnyChildMapped)
+ {
+ HumanBoneList::const_iterator it2 = std::find_if(humanDescription.m_Human.begin(), humanDescription.m_Human.end(), FindBoneName( transfom.GetName() ));
+ if(it2==humanDescription.m_Human.end())
+ {
+ namedTransform.erase(namedTransform.begin() + n);
+ n--;
+ didRemove = true;
+ }
+ }
+ }
+
+ return didRemove;
+}
+
+void AvatarBuilder::ReadFromLocalTransformToSkeletonPose(mecanim::skeleton::SkeletonPose* pose, NamedTransforms const& namedTransform)
+{
+ int j;
+ for(j=0;j<namedTransform.size();j++)
+ {
+ Transform& transform = *namedTransform[j].transform;
+ pose->m_X[j] = xformFromUnity(transform.GetLocalPosition(), transform.GetLocalRotation(), transform.GetLocalScale());
+ }
+}
+
+bool AvatarBuilder::TPoseMatch(mecanim::animation::AvatarConstant const& avatar, NamedTransforms const& namedTransform, std::string& warning)
+{
+ bool ret = true;
+ if(avatar.isHuman())
+ {
+ mecanim::memory::MecanimAllocator alloc(kMemTempAlloc);
+
+ mecanim::skeleton::SkeletonPose* avatarSKPose = mecanim::skeleton::CreateSkeletonPose(avatar.m_AvatarSkeleton.Get(), alloc);
+ mecanim::skeleton::SkeletonPose* avatarHumanPose = mecanim::skeleton::CreateSkeletonPose(avatar.m_Human->m_Skeleton.Get(), alloc);
+ mecanim::skeleton::SkeletonPose* avatarGPose1 = mecanim::skeleton::CreateSkeletonPose(avatar.m_Human->m_Skeleton.Get(), alloc);
+ mecanim::skeleton::SkeletonPose* avatarGPose2 = mecanim::skeleton::CreateSkeletonPose(avatar.m_Human->m_Skeleton.Get(), alloc);
+
+ ReadFromLocalTransformToSkeletonPose(avatarSKPose, namedTransform);
+ mecanim::skeleton::SkeletonPoseCopy(avatar.m_AvatarSkeleton.Get(),avatarSKPose,avatar.m_Human->m_Skeleton.Get(), avatarHumanPose);
+
+ mecanim::skeleton::SkeletonPoseComputeGlobal(avatar.m_Human->m_Skeleton.Get(), avatarHumanPose, avatarGPose1);
+ mecanim::skeleton::SkeletonPoseComputeGlobal(avatar.m_Human->m_Skeleton.Get(), avatar.m_Human->m_SkeletonPose.Get(), avatarGPose2);
+
+ // Do not check for human hips bone lenght, because an animation is not guarantee to start at origin.
+ mecanim::uint32_t i = avatar.m_Human->m_HumanBoneIndex[mecanim::human::kHips + 1];
+ for(;i<avatar.m_Human->m_Skeleton->m_Count;++i)
+ {
+ mecanim::int32_t parentId = avatar.m_Human->m_Skeleton->m_Node[i].m_ParentId;
+ if(parentId != -1)
+ {
+ float len1 = math::length(avatarGPose1->m_X[i].t - avatarGPose1->m_X[parentId].t).tofloat();
+ float len2 = math::length(avatarGPose2->m_X[i].t - avatarGPose2->m_X[parentId].t).tofloat();
+
+ if( math::abs(len1-len2) > M_EPSF && math::abs(len2) > M_EPSF)
+ {
+ float ratio = len1/len2;
+
+ if( math::abs(1.f-ratio) > 0.30f)
+ {
+ warning += Format("'%s' : avatar = %.2f, animation = %.2f\n", namedTransform[avatar.m_HumanSkeletonIndexArray[i]].transform->GetName(), len2, len1);
+ ret = false;
+ }
+ }
+ }
+ }
+
+ mecanim::skeleton::DestroySkeletonPose(avatarSKPose, alloc);
+ mecanim::skeleton::DestroySkeletonPose(avatarHumanPose, alloc);
+ mecanim::skeleton::DestroySkeletonPose(avatarGPose1, alloc);
+ mecanim::skeleton::DestroySkeletonPose(avatarGPose2, alloc);
+ }
+
+ return ret;
+}
diff --git a/Runtime/Animation/AvatarBuilder.h b/Runtime/Animation/AvatarBuilder.h
new file mode 100644
index 0000000..03eb8ae
--- /dev/null
+++ b/Runtime/Animation/AvatarBuilder.h
@@ -0,0 +1,256 @@
+#ifndef AVATARBUILDER_H
+#define AVATARBUILDER_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/mecanim/types.h"
+
+#include "Runtime/Animation/MecanimUtility.h"
+
+#include "Runtime/Serialize/SerializeTraits.h"
+
+#include <vector>
+
+namespace Unity { class GameObject; }
+class Avatar;
+class Transform;
+
+namespace mecanim
+{
+ namespace skeleton
+ {
+ struct SkeletonPose;
+ struct Skeleton;
+ }
+
+ namespace animation
+ {
+ struct AvatarConstant;
+ }
+}
+
+struct SkeletonBone
+{
+ DEFINE_GET_TYPESTRING(SkeletonBone)
+
+ SkeletonBone()
+ :m_Position(Vector3f::zero),
+ m_Rotation(Quaternionf::identity()),
+ m_Scale(Vector3f::one),
+ m_TransformModified(false)
+ {
+ }
+
+ UnityStr m_Name;
+ Vector3f m_Position;
+ Quaternionf m_Rotation;
+ Vector3f m_Scale;
+ bool m_TransformModified;
+
+ template<class TransferFunction>
+ inline void Transfer (TransferFunction& transfer)
+ {
+ TRANSFER(m_Name);
+ TRANSFER(m_Position);
+ TRANSFER(m_Rotation);
+ TRANSFER(m_Scale);
+ TRANSFER(m_TransformModified);
+ transfer.Align();
+ }
+};
+
+struct SkeletonBoneLimit
+{
+ DEFINE_GET_TYPESTRING(SkeletonBoneLimit)
+
+ SkeletonBoneLimit()
+ :m_Min(Vector3f::zero),
+ m_Max(Vector3f::zero),
+ m_Value(Vector3f::zero),
+ m_Length(0.f),
+ m_Modified(false)
+ {
+ }
+
+ Vector3f m_Min;
+ Vector3f m_Max;
+ Vector3f m_Value;
+ float m_Length;
+ bool m_Modified;
+
+ template<class TransferFunction>
+ inline void Transfer (TransferFunction& transfer)
+ {
+ TRANSFER(m_Min);
+ TRANSFER(m_Max);
+ TRANSFER(m_Value);
+ TRANSFER(m_Length);
+ TRANSFER(m_Modified);
+ transfer.Align();
+ }
+};
+
+
+struct HumanBone
+{
+ DEFINE_GET_TYPESTRING(HumanBone)
+
+ HumanBone();
+ HumanBone(std::string const& humanName);
+
+ UnityStr m_BoneName;
+ UnityStr m_HumanName;
+ SkeletonBoneLimit m_Limit;
+ //Vector3f m_ColliderPosition;
+ //Quaternionf m_ColliderRotation;
+ //Vector3f m_ColliderScale;
+
+ template<class TransferFunction>
+ inline void Transfer (TransferFunction& transfer)
+ {
+ TRANSFER(m_BoneName);
+ TRANSFER(m_HumanName);
+ TRANSFER(m_Limit);
+
+ //TRANSFER(m_ColliderPosition);
+ //TRANSFER(m_ColliderRotation);
+ //TRANSFER(m_ColliderScale);
+ }
+};
+
+class FindHumanBone
+{
+protected:
+ UnityStr mName;
+public:
+ FindHumanBone(UnityStr const& name):mName(name){}
+ bool operator()(HumanBone const& bone){ return mName == bone.m_HumanName;}
+};
+
+class FindBoneName
+{
+protected:
+ UnityStr mName;
+public:
+ FindBoneName(UnityStr const& name):mName(name){}
+ bool operator()(HumanBone const& bone){ return mName == bone.m_BoneName;}
+};
+
+typedef std::vector<HumanBone> HumanBoneList;
+typedef std::vector<SkeletonBone> SkeletonBoneList;
+
+struct HumanDescription
+{
+ HumanBoneList m_Human;
+ SkeletonBoneList m_Skeleton;
+
+ float m_ArmTwist;
+ float m_ForeArmTwist;
+ float m_UpperLegTwist;
+ float m_LegTwist;
+
+ float m_ArmStretch;
+ float m_LegStretch;
+
+ float m_FeetSpacing;
+
+ UnityStr m_RootMotionBoneName;
+
+ void Reset()
+ {
+ m_Human.clear();
+ m_Skeleton.clear();
+
+ m_ArmTwist = 0.5f;
+ m_ForeArmTwist = 0.5f;
+ m_UpperLegTwist = 0.5f;
+ m_LegTwist = 0.5f;
+ m_ArmStretch = 0.05f;
+ m_LegStretch = 0.05f;
+
+ m_FeetSpacing = 0.0f;
+
+ m_RootMotionBoneName = "";
+ }
+
+ DEFINE_GET_TYPESTRING(HumanDescription)
+
+ template<class TransferFunction>
+ inline void Transfer (TransferFunction& transfer)
+ {
+ TRANSFER(m_Human);
+ TRANSFER(m_Skeleton);
+
+ TRANSFER(m_ArmTwist);
+ TRANSFER(m_ForeArmTwist);
+ TRANSFER(m_UpperLegTwist);
+ TRANSFER(m_LegTwist);
+ TRANSFER(m_ArmStretch);
+ TRANSFER(m_LegStretch);
+ TRANSFER(m_FeetSpacing);
+ TRANSFER(m_RootMotionBoneName);
+ }
+};
+
+// The type of Avatar to create
+enum AvatarType
+{
+ kGeneric = 2,
+ kHumanoid = 3
+};
+
+class AvatarBuilder
+{
+public:
+ struct Options
+ {
+ Options():avatarType(kGeneric), readTransform(false), useMask(false){}
+
+ AvatarType avatarType;
+ bool readTransform;
+ bool useMask;
+ };
+
+ struct NamedTransform
+ {
+ UnityStr name;
+ UnityStr path;
+ Transform* transform;
+ };
+
+ typedef std::vector<NamedTransform> NamedTransforms;
+
+ // readTransform is mainly used by importer. It does read the Default pose from the first frame pose found in the file.
+ // useMask is mainly used for API call, when suer want to select only a sub part of a hierarchy.
+ static std::string BuildAvatar(Avatar& avatar, const Unity::GameObject& go, bool doOptimizeTransformHierarchy, const HumanDescription& humanDescription, Options options = Options() );
+
+ static bool IsValidHuman(HumanDescription const& humanDescription, NamedTransforms const& namedTransform, std::string& error);
+ static bool IsValidHumanDescription(HumanDescription const& humanDescription, std::string& error);
+
+ static std::string GenerateAvatarMap(Unity::GameObject const& go, NamedTransforms& namedTransform, const HumanDescription& humanDescription, bool doOptimizeTransformHierarchy, AvatarType avatarType, bool useMask = false);
+
+ static void GetAllChildren(Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask = std::vector<UnityStr>() );
+ static void GetAllParent(Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask = std::vector<UnityStr>(), bool includeSelf = false );
+
+ static void ReadFromLocalTransformToSkeletonPose(mecanim::skeleton::SkeletonPose* pose, NamedTransforms const& namedTransform);
+
+ static bool TPoseMatch(mecanim::animation::AvatarConstant const& avatar, NamedTransforms const& namedTransform, std::string& warning);
+protected:
+
+ static void GetAllChildren (Transform& node, UnityStr& path, NamedTransforms& transforms, std::vector<UnityStr> const& mask);
+ static void GetAllParent(Transform& root, Transform& node, NamedTransforms& transforms, std::vector<UnityStr> const& mask = std::vector<UnityStr>(), bool includeSelf = false );
+
+ static Transform* GetTransform(int id, HumanDescription const& humanDescription, NamedTransforms const& namedTransform, std::vector<string> const& boneName);
+
+
+ static Transform* GetHipsNode(const HumanDescription& humanDescription, NamedTransforms const& transforms);
+ static Transform* GetRootMotionNode(const HumanDescription& humanDescription, NamedTransforms const& transforms);
+ static bool RemoveAllNoneHumanLeaf(NamedTransforms& namedTransform, HumanDescription const& humanDescription);
+};
+
+
+
+
+#endif
diff --git a/Runtime/Animation/AvatarPlayback.cpp b/Runtime/Animation/AvatarPlayback.cpp
new file mode 100644
index 0000000..1fd09af
--- /dev/null
+++ b/Runtime/Animation/AvatarPlayback.cpp
@@ -0,0 +1,118 @@
+#include "UnityPrefix.h"
+
+#include "AvatarPlayback.h"
+
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+#include "Runtime/Animation/MecanimUtility.h"
+#include "Runtime/Utilities/LogAssert.h"
+
+enum { kMaxFrameCount = 10000};
+
+AvatarPlayback::AvatarPlayback(MemLabelId label)
+: m_Alloc(label),
+ m_FrameCount(-1),
+ m_CursorIndex(-1),
+ m_StartIndex(-1),
+ m_StopIndex(-1)
+{
+}
+
+void AvatarPlayback::Init(int frameCount)
+{
+ Clear();
+ if(frameCount > 0 )
+ {
+ if(frameCount > kMaxFrameCount )
+ WarningString("Could not allocate requested frameCount for Animator Recording. 10000 frames where allocated.");
+
+ m_Frames.resize(min<unsigned int>(frameCount,kMaxFrameCount));
+ m_FrameCount = m_Frames.size();
+ }
+ else
+ {
+ m_FrameCount = 0 ;
+ }
+
+ m_CursorIndex = -1;
+ m_StartIndex = -1 ;
+ m_StopIndex = -1;
+}
+
+void AvatarPlayback::Clear()
+{
+ for(int i = 0 ; i < m_Frames.size() ; i++)
+ {
+ mecanim::animation::DestroyAvatarMemory(m_Frames[i].m_AvatarMemory, m_Alloc);
+ }
+ m_Frames.clear();
+}
+
+int AvatarPlayback::NextIndex(int index)
+{
+ return m_FrameCount > 0 ? (index+1)%m_FrameCount : index+1;
+}
+
+mecanim::animation::AvatarMemory* AvatarPlayback::PlayFrame(float time, float &effectiveTime)
+{
+ int frameIndex = m_StopIndex;
+ bool found = false;
+
+ if(m_StartIndex == -1)
+ return 0;
+
+ int i = m_StartIndex;
+ int prevIndex = i;
+
+ int endIndex = NextIndex(m_StopIndex);
+ do // at this point, we have a least one frame recorded
+ {
+ if(m_Frames[i].m_CurrentTime > time)
+ {
+ frameIndex = prevIndex;
+ found = true;
+ }
+
+ prevIndex = i;
+ i = NextIndex(i);
+ } while(i != endIndex && !found);
+
+
+ effectiveTime = m_Frames[frameIndex].m_CurrentTime;
+ m_CursorIndex = frameIndex;
+
+ return m_Frames[frameIndex].m_AvatarMemory;
+}
+
+void AvatarPlayback::RecordFrame(float deltaTime, const mecanim::animation::AvatarMemory* srcMemory)
+{
+ if(m_FrameCount == -1)
+ {
+ WarningString("Could not record Animator. Frame allocation has failed.");
+ return;
+ }
+
+ AvatarFrame newFrame;
+ if(m_StartIndex != -1)
+ newFrame.m_CurrentTime = m_Frames[m_CursorIndex].m_CurrentTime + deltaTime;
+
+ size_t size=0;
+ newFrame.m_AvatarMemory = CopyBlob( *srcMemory, m_Alloc, size);
+
+ m_CursorIndex = NextIndex(m_CursorIndex);
+ if(m_StartIndex == m_CursorIndex|| m_StartIndex == -1) // increment startIndex when the cursor is writing on it (the buffer is full)
+ {
+ if(m_StartIndex != -1)
+ mecanim::animation::DestroyAvatarMemory(m_Frames[m_CursorIndex].m_AvatarMemory, m_Alloc);
+ m_StartIndex = NextIndex(m_StartIndex);
+ }
+ m_StopIndex = m_CursorIndex;
+ if(m_FrameCount > 0)
+ m_Frames[m_CursorIndex] = newFrame;
+ else
+ m_Frames.push_back(newFrame);
+}
+
+float AvatarPlayback::CursorTime() {return m_CursorIndex != -1 ? m_Frames[m_CursorIndex].m_CurrentTime : -1;}
+float AvatarPlayback::StartTime() {return m_CursorIndex != -1 ? m_Frames[m_StartIndex].m_CurrentTime : -1;}
+float AvatarPlayback::StopTime() {return m_CursorIndex != -1 ? m_Frames[m_StopIndex].m_CurrentTime : -1;}
diff --git a/Runtime/Animation/AvatarPlayback.h b/Runtime/Animation/AvatarPlayback.h
new file mode 100644
index 0000000..75e9ff4
--- /dev/null
+++ b/Runtime/Animation/AvatarPlayback.h
@@ -0,0 +1,51 @@
+#ifndef AVATARPLAYBACK_H
+#define AVATARPLAYBACK_H
+
+#include "Runtime/Animation/MecanimUtility.h"
+
+namespace mecanim
+{
+ namespace animation
+ {
+ struct AvatarMemory ;
+ }
+}
+
+class AvatarFrame
+{
+public:
+ AvatarFrame() : m_AvatarMemory(0), m_CurrentTime(0) {}
+ mecanim::animation::AvatarMemory* m_AvatarMemory;
+ float m_CurrentTime;
+};
+
+class AvatarPlayback
+{
+public:
+
+ AvatarPlayback(MemLabelId label);
+
+ void Clear();
+ mecanim::animation::AvatarMemory* PlayFrame(float time, float& effectiveTime);
+ void RecordFrame(float deltaTime, const mecanim::animation::AvatarMemory* srcMemory);
+
+ void Init(int frameCount);
+
+ float StartTime();
+ float StopTime();
+ float CursorTime();
+
+
+private:
+ std::vector<AvatarFrame> m_Frames;
+
+ int m_FrameCount;
+ int m_StartIndex;
+ int m_StopIndex;
+ int m_CursorIndex;
+
+ int NextIndex(int index);
+ mecanim::memory::MecanimAllocator m_Alloc;
+};
+
+#endif // AVATARPLAYBACK_H
diff --git a/Runtime/Animation/BaseAnimationTrack.cpp b/Runtime/Animation/BaseAnimationTrack.cpp
new file mode 100644
index 0000000..6dafca6
--- /dev/null
+++ b/Runtime/Animation/BaseAnimationTrack.cpp
@@ -0,0 +1,15 @@
+#include "UnityPrefix.h"
+
+#if UNITY_EDITOR
+#include "BaseAnimationTrack.h"
+
+IMPLEMENT_CLASS (BaseAnimationTrack)
+
+BaseAnimationTrack::BaseAnimationTrack(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{}
+
+BaseAnimationTrack::~BaseAnimationTrack()
+{}
+
+#endif
diff --git a/Runtime/Animation/BaseAnimationTrack.h b/Runtime/Animation/BaseAnimationTrack.h
new file mode 100644
index 0000000..c3f2b40
--- /dev/null
+++ b/Runtime/Animation/BaseAnimationTrack.h
@@ -0,0 +1,21 @@
+#if UNITY_EDITOR
+#ifndef BASEANIMATIONTRACK_H
+#define BASEANIMATIONTRACK_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+
+template<class T> class AnimationCurveTpl;
+typedef AnimationCurveTpl<float> AnimationCurve;
+
+class BaseAnimationTrack : public NamedObject
+{
+ public:
+
+ REGISTER_DERIVED_ABSTRACT_CLASS (BaseAnimationTrack, NamedObject)
+
+ BaseAnimationTrack(MemLabelId label, ObjectCreationMode mode);
+ // ~BaseAnimationTrack (); declared-by-macro
+};
+
+#endif
+#endif
diff --git a/Runtime/Animation/BoundCurve.h b/Runtime/Animation/BoundCurve.h
new file mode 100644
index 0000000..5a73169
--- /dev/null
+++ b/Runtime/Animation/BoundCurve.h
@@ -0,0 +1,51 @@
+#pragma once
+
+class IAnimationBinding;
+
+namespace UnityEngine
+{
+namespace Animation
+{
+
+
+// These values can can never be changed, when a system is deprecated,
+// it must be kept commented out and the index is not to be reused!
+// The enum value can not be reused. Otherwise built data (assetbundles) will break.
+enum BindType
+{
+ kUnbound = 0,
+
+ // Builtin transform bindings
+ kBindTransformPosition = 1, // This enum may not be changed. It is used in GenericClipBinding.
+ kBindTransformRotation = 2, // This enum may not be changed. It is used in GenericClipBinding.
+ kBindTransformScale = 3, // It is used in GenericClipBinding.
+
+ // Builtin float bindings
+ kMinSinglePropertyBinding = 5,
+ kBindFloat = 5,
+ kBindFloatToBool = 6,
+ kBindGameObjectActive = 7,
+ kBindMuscle = 8,
+
+
+ // Custom bindings
+ kBlendShapeWeightBinding = 20,
+ kRendererMaterialPPtrBinding = 21,
+ kRendererMaterialPropertyBinding = 22,
+ kSpriteRendererPPtrBinding = 23,
+ kMonoBehaviourPropertyBinding = 24,
+ kAllBindingCount
+};
+
+struct BoundCurve
+{
+ void* targetPtr;
+ UInt32 targetType;
+ IAnimationBinding* customBinding;
+ Object* targetObject;
+
+ BoundCurve () { targetObject = 0; targetPtr = 0; targetType = 0; customBinding = 0; }
+};
+
+}
+}
diff --git a/Runtime/Animation/BoundCurveDeprecated.h b/Runtime/Animation/BoundCurveDeprecated.h
new file mode 100644
index 0000000..0edc24e
--- /dev/null
+++ b/Runtime/Animation/BoundCurveDeprecated.h
@@ -0,0 +1,60 @@
+#pragma once
+
+
+struct BoundCurveDeprecated
+{
+ int targetInstanceID;
+
+ // The ptr to the data we are writing into
+ void* targetPtr;
+
+ // type of the data we will write into kBindFloatValue, kBindQuaternion etc.
+ // - Materials: 4 bits targetType, 24 bits shader property name, 4 bits sub property index (x, y, z, w)
+ // - SkinnedMeshRenderer: 4 bits targetType, the rest BlendShape weight index
+ UInt32 targetType;
+
+ // Which states affect the bound curve?
+ UInt32 affectedStateMask;
+
+ Object* targetObject;
+
+ BoundCurveDeprecated () { targetObject = NULL; targetType = 0; targetInstanceID = 0; targetPtr = 0; affectedStateMask = 0; }
+
+ enum
+ {
+ kBindTypeBitCount = 4,
+ kBindMaterialShaderPropertyNameBitCount = 24,
+
+ kBindTypeMask = (1 << kBindTypeBitCount) - 1,
+ kBindMaterialShaderPropertyNameMask = ((1 << kBindMaterialShaderPropertyNameBitCount) - 1) << kBindTypeBitCount,
+ kBindMaterialShaderSubpropertyMask = ~(kBindTypeMask | kBindMaterialShaderPropertyNameMask),
+ };
+};
+
+enum
+{
+ kUnbound = 0,
+ kBindTransformPosition = 1, // This enum may not be changed. It is used in GenericClipBinding.
+ kBindTransformRotation = 2, // This enum may not be changed. It is used in GenericClipBinding.
+ kBindTransformScale = 3, // It is used in GenericClipBinding.
+ kMinGenericBinding = 4,
+
+ kBindFloat,
+ kBindFloatToBool,
+ kBindFloatToBlendShapeWeight,
+ kBindFloatToGameObjectActivate,
+
+ kMinIntCurveBinding,
+ kBindMaterialPPtrToRenderer = kMinIntCurveBinding,
+#if ENABLE_SPRITES
+ kBindSpritePPtrToSpriteRenderer,
+#endif
+ kMaxIntCurveBinding,
+
+ // These must always come last since materials do special bit masking magic.
+ kBindFloatToMaterial = kMaxIntCurveBinding,
+ kBindFloatToColorMaterial,
+ kBindFloatToMaterialScaleAndOffset,
+
+ kBindTypeCount
+};
diff --git a/Runtime/Animation/CalculateAnimatorSkinMatrices.cpp b/Runtime/Animation/CalculateAnimatorSkinMatrices.cpp
new file mode 100644
index 0000000..4e2091a
--- /dev/null
+++ b/Runtime/Animation/CalculateAnimatorSkinMatrices.cpp
@@ -0,0 +1,85 @@
+#include "UnityPrefix.h"
+
+#include "Runtime/Animation/CalculateAnimatorSkinMatrices.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Animation/MecanimUtility.h"
+#include "Runtime/Filters/Mesh/MeshSkinning.h"
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Profiler/Profiler.h"
+
+PROFILER_INFORMATION(gMeshSkinningPullMatrices, "MeshSkinning.CalculateSkinningMatrices", kProfilerRender)
+
+void* DoCalculateAnimatorSkinMatrices (void* userData)
+{
+ PROFILER_AUTO(gMeshSkinningPullMatrices, NULL);
+
+ CalculateSkinMatricesTask& task = *static_cast<CalculateSkinMatricesTask*>(userData);
+ const mecanim::skeleton::SkeletonPose* animatedPose =
+ reinterpret_cast<const mecanim::skeleton::SkeletonPose*>(task.skeletonPose);
+
+ for (int i=0; i<task.bindPoseCount; i++)
+ {
+ UInt16 skeletonIndex = task.skeletonIndices[i];
+ xform2unity (animatedPose->m_X[skeletonIndex], task.outPose[i]);
+ }
+
+ MultiplyMatrixArrayWithBase4x4 (&task.rootPose, task.outPose, task.bindPose, task.outPose, task.bindPoseCount);
+ return NULL;
+}
+
+static void CalculateDefaultPoseWorldSpaceMatrices (
+ const Transform& transform,
+ const mecanim::animation::AvatarConstant* avatarConstant,
+ const UInt16* skeletonIndices,
+ Matrix4x4f* outWorldSpaceMatrices,
+ size_t size)
+{
+ // Extract default skeleton pose from avatar and setup root position according to transform.
+ Assert (avatarConstant);
+
+ mecanim::memory::MecanimAllocator tempAlloc (kMemTempAlloc);
+ mecanim::skeleton::SkeletonPose* tempDefaultGPose = mecanim::skeleton::CreateSkeletonPose(avatarConstant->m_AvatarSkeleton.Get(), tempAlloc);
+
+ SkeletonPoseCopy (avatarConstant->m_DefaultPose.Get(), tempDefaultGPose);
+ tempDefaultGPose->m_X[0] = xformFromUnity (transform.GetPosition(), transform.GetRotation(), transform.GetWorldScaleLossy());
+ mecanim::skeleton::SkeletonPoseComputeGlobal (avatarConstant->m_AvatarSkeleton.Get(), tempDefaultGPose, tempDefaultGPose);
+ for (int i=0;i<size;i++)
+ {
+ UInt16 skeletonIndex = skeletonIndices[i];
+ xform2unity (tempDefaultGPose->m_X[skeletonIndex], outWorldSpaceMatrices[i]);
+ }
+
+ mecanim::skeleton::DestroySkeletonPose(tempDefaultGPose, tempAlloc);
+}
+
+bool CalculateWordSpaceMatrices (Animator* animator, const UInt16* skeletonIndices, Matrix4x4f* outWorldSpaceMatrices, size_t size)
+{
+ const mecanim::skeleton::SkeletonPose* animatedPose = animator->GetGlobalSpaceSkeletonPose ();
+
+ if (animatedPose)
+ {
+ for (int i=0;i<size;i++)
+ {
+ UInt16 skeletonIndex = skeletonIndices[i];
+ xform2unity (animatedPose->m_X[skeletonIndex], outWorldSpaceMatrices[i]);
+ }
+ }
+ else
+ {
+ const mecanim::animation::AvatarConstant* avatarConstant = animator->GetAvatarConstant ();
+ if (avatarConstant == NULL)
+ {
+ // Slow code path:
+ Avatar* avatar = animator->GetAvatar ();
+ if (avatar)
+ avatarConstant = avatar->GetAsset ();
+ if (avatarConstant == NULL)
+ return false;
+ }
+ CalculateDefaultPoseWorldSpaceMatrices (animator->GetComponent(Transform), avatarConstant, skeletonIndices, outWorldSpaceMatrices, size);
+ }
+ return true;
+}
diff --git a/Runtime/Animation/CalculateAnimatorSkinMatrices.h b/Runtime/Animation/CalculateAnimatorSkinMatrices.h
new file mode 100644
index 0000000..a0ee939
--- /dev/null
+++ b/Runtime/Animation/CalculateAnimatorSkinMatrices.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Runtime/Interfaces/IAnimation.h"
+
+class Animator;
+
+// Multi-threaded.
+// Can only get animated pose.
+// Bindpose included.
+void* DoCalculateAnimatorSkinMatrices (void* userData);
+
+// Main thread.
+// Can handle both cases: animated pose & default pose.
+// Bindpose not included.
+bool CalculateWordSpaceMatrices (Animator* animator, const UInt16* skeletonIndices, Matrix4x4f* outWorldSpaceMatrices, size_t size);
+
+
+
diff --git a/Runtime/Animation/CharacterTestFixture.h b/Runtime/Animation/CharacterTestFixture.h
new file mode 100644
index 0000000..cd72c5e
--- /dev/null
+++ b/Runtime/Animation/CharacterTestFixture.h
@@ -0,0 +1,202 @@
+#pragma once
+
+#if ENABLE_UNIT_TESTS
+
+#include "Runtime/Testing/Testing.h"
+#include "Runtime/Testing/TestFixtures.h"
+
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/Animation/AvatarBuilder.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+
+#include "Runtime/Animation/OptimizeTransformHierarchy.h"
+
+
+// y
+// |
+// |
+// 3 b1_2_2
+// | | mr2
+// | | |
+// 2 b1_2----b1_2_1 b2_1_2-----b2_1_2_1
+// | | mr1 |
+// | | | |
+// 1 b1-----b1_1---b1_1_1 b2-----b2_1------b2_1_1
+// |
+// 0____1_______2_______3_________4_______5_________6_________ x
+//
+// smr1 {b1(root), b1_1, b1_1_1, b1_2, b1_2_1, b1_2_2}
+// smr2 {b2_1_1, b2_1_2, b2_1_2_1}
+
+struct TransformDescriptor {
+ const char* path;
+ float t[3];
+ float r[3];
+ float s[3];
+};
+const TransformDescriptor BONE_ARRAY[] = {
+ {"b1", {1,1,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_1", {2,1,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_1/b1_1_1", {3,1,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_2", {1,2,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_2/b1_2_1", {2,2,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_2/b1_2_1/tobeStrip1", {5,5,5}, {0,0,0}, {1,1,1} },
+ {"b1/b1_2/b1_2_2", {1,3,0}, {0,0,0}, {1,1,1} },
+ {"b2", {4,1,0}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1", {5,1,0}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1/b2_1_1", {6,1,0}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1/b2_1_2", {5,2,0}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1/b2_1_2/b2_1_2_1", {6,2,0}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1/b2_1_2/b2_1_2_1/tobeStrip3", {6,6,6}, {0,0,0}, {1,1,1} },
+ {"tobeStrip2", {7,7,7}, {0,0,0}, {1,1,1} },
+};
+const int BONE_COUNT = sizeof(BONE_ARRAY)/sizeof(BONE_ARRAY[0]);
+const TransformDescriptor MESH_RENDERER_ARRAY[] = {
+ {"b1/b1_1/b1_1_1/mr1", {3,1.5,0}, {0,0,0}, {1,1,1} },
+ {"b1/b1_2/b1_2_1/mr2", {2,2.5,0}, {0,0,0}, {1,1,1} },
+};
+const int MESH_RENDERER_COUNT = sizeof(MESH_RENDERER_ARRAY)/sizeof(MESH_RENDERER_ARRAY[0]);
+const TransformDescriptor SKINNED_MESH_RENDERER_ARRAY[] = {
+ {"smr1", {8,8,8}, {0,0,0}, {1,1,1} },
+ {"b2/b2_1/smr2", {9,9,9}, {0,0,0}, {1,1,1} },
+};
+const int SKINNED_MESH_RENDERER_COUNT = sizeof(SKINNED_MESH_RENDERER_ARRAY)/sizeof(SKINNED_MESH_RENDERER_ARRAY[0]);
+
+class CharacterTestFixture : public TestFixtureBase
+{
+protected:
+ Unity::GameObject* root;
+ Avatar* avatar;
+ Avatar* unstrippedAvatar;
+
+ CharacterTestFixture():root(NULL), avatar(NULL), unstrippedAvatar(NULL) {}
+
+ ~CharacterTestFixture()
+ {
+ DestroyObjects();
+ }
+
+ void MakeCharacter(const UnityStr* extraExposedPaths = NULL, int extraExposedPathCount = 0)
+ {
+ root = &CreateGameObjectWithHideFlags("root", true, 0, "Transform", "Animator", NULL);
+ Transform& rootTr = root->GetComponent(Transform);
+ AttachGameObjects(rootTr);
+
+ CreateAvatars(extraExposedPaths, extraExposedPathCount);
+
+ Animator& animator = root->GetComponent(Animator);
+ animator.SetAvatar(avatar);
+ animator.AwakeFromLoad(kDefaultAwakeFromLoad);
+ }
+
+ void DestroyObjects()
+ {
+ if (root)
+ {
+ DestroyObjectHighLevel(root);
+ root = NULL;
+ }
+ if (avatar)
+ {
+ DestroyObjectHighLevel(avatar);
+ avatar = NULL;
+ }
+ if (unstrippedAvatar)
+ {
+ DestroyObjectHighLevel(unstrippedAvatar);
+ unstrippedAvatar = NULL;
+ }
+ }
+
+ void CreateAvatars(const UnityStr* extraExposedPaths = NULL, int extraExposedPathCount = 0)
+ {
+ HumanDescription dummyHd;
+ std::string errStr;
+
+ AvatarBuilder::Options options;
+ options.avatarType = kGeneric;
+ options.readTransform = true;
+
+ //// 1. create unstripped avatar
+ //unstrippedAvatar = NEW_OBJECT (Avatar);
+ //unstrippedAvatar->Reset();
+ //errStr = AvatarBuilder::BuildAvatar(*unstrippedAvatar, *root, true, dummyHd, options);
+ //CHECK_EQUAL (string(""), errStr);
+ //unstrippedAvatar->AwakeFromLoad(kDefaultAwakeFromLoad);
+
+ //// 2. strip
+ //RemoveUnnecessaryTransforms(*root,
+ // &dummyHd,
+ // extraExposedPaths,
+ // extraExposedPathCount,
+ // true);
+
+ // 3. create stripped avatar
+ avatar = NEW_OBJECT (Avatar);
+ avatar->Reset();
+
+ errStr = AvatarBuilder::BuildAvatar(*avatar,
+ *root, true, dummyHd, options);
+ CHECK_EQUAL (string(""), errStr);
+ avatar->AwakeFromLoad(kDefaultAwakeFromLoad);
+ }
+
+ void AttachGameObjects(Transform& rootTr)
+ {
+ dynamic_array<PPtr<Transform> > smrBones[SKINNED_MESH_RENDERER_COUNT];
+
+ dynamic_array<TransformDescriptor> transforms;
+ for (int i = 0; i < BONE_COUNT; i++) transforms.push_back(BONE_ARRAY[i]);
+ for (int i = 0; i < MESH_RENDERER_COUNT; i++) transforms.push_back(MESH_RENDERER_ARRAY[i]);
+ for (int i = 0; i < SKINNED_MESH_RENDERER_COUNT; i++) transforms.push_back(SKINNED_MESH_RENDERER_ARRAY[i]);
+ for (int i = 0; i < transforms.size(); i++)
+ {
+ string path(transforms[i].path);
+
+ string parentPath = DeleteLastPathNameComponent(path);
+ Transform* parent = FindRelativeTransformWithPath(rootTr, parentPath.c_str());
+ CHECK(parent != NULL);
+
+ string goName = GetLastPathNameComponent(path);
+ GameObject& go = CreateGameObjectWithHideFlags(goName, true, 0, "Transform", NULL);
+ Transform& tr = go.GetComponent(Transform);
+ tr.SetPosition(Vector3f(transforms[i].t[0], transforms[i].t[1], transforms[i].t[2]));
+ tr.SetParent(parent);
+
+ if (goName.compare(0, strlen("b1"), "b1") == 0)
+ smrBones[0].push_back(&tr);
+ else if (goName.compare(0, strlen("b2_1_"), "b2_1_") == 0)
+ smrBones[1].push_back(&tr);
+ }
+
+ for (int i = 0; i < MESH_RENDERER_COUNT; i++)
+ {
+ Transform* tr = FindRelativeTransformWithPath(rootTr, MESH_RENDERER_ARRAY[i].path);
+ AddComponent(tr->GetGameObject(), "MeshRenderer");
+ }
+
+ for (int i = 0; i < SKINNED_MESH_RENDERER_COUNT; i++)
+ {
+ Transform* tr = FindRelativeTransformWithPath(rootTr, SKINNED_MESH_RENDERER_ARRAY[i].path);
+ AddComponent(tr->GetGameObject(), "SkinnedMeshRenderer");
+ SkinnedMeshRenderer& smr = tr->GetComponent(SkinnedMeshRenderer);
+ if (i == 0)
+ smr.SetRootBone(smrBones[i][0]);
+ smr.SetBones(smrBones[i]);
+ }
+ }
+
+ int GetAllChildrenCount(Transform& tr)
+ {
+ int count = tr.GetChildrenCount();
+ for (int i = 0; i < tr.GetChildrenCount(); i++)
+ count += GetAllChildrenCount(tr.GetChild(i));
+ return count;
+ }
+
+};
+
+#endif
diff --git a/Runtime/Animation/DenseClipBuilder.cpp b/Runtime/Animation/DenseClipBuilder.cpp
new file mode 100644
index 0000000..4fd800d
--- /dev/null
+++ b/Runtime/Animation/DenseClipBuilder.cpp
@@ -0,0 +1,97 @@
+#include "UnityPrefix.h"
+#include "DenseClipBuilder.h"
+#include "Runtime/mecanim/memory.h"
+
+void CreateDenseClip(mecanim::animation::DenseClip& clip, UInt32 curveCount, float begin, float end, float sampleRate, mecanim::memory::Allocator& alloc)
+{
+ // We always need at least one frame to sample the clip
+ clip.m_FrameCount = std::max(CeilfToInt ((end - begin) * sampleRate), 1);
+ clip.m_CurveCount = curveCount;
+ clip.m_SampleRate = sampleRate;
+ clip.m_BeginTime = begin;
+
+ clip.m_SampleArraySize = clip.m_FrameCount * clip.m_CurveCount;
+ clip.m_SampleArray = alloc.ConstructArray<float>(clip.m_SampleArraySize);
+}
+
+template<class T>
+void AddCurveToDenseClip(mecanim::animation::DenseClip& clip, int curveIndex, const AnimationCurveTpl<T>& curve)
+{
+ for (int i=0;i<clip.m_FrameCount;i++)
+ {
+ float time = clip.m_BeginTime + ((float)i / clip.m_SampleRate);
+
+ float* dst = &clip.m_SampleArray[i * clip.m_CurveCount + curveIndex];
+
+ T value = curve.EvaluateClamp(time);
+ *reinterpret_cast<T*> (dst) = value;
+
+ }
+}
+
+template void AddCurveToDenseClip<float>(mecanim::animation::DenseClip& clip, int curveIndex, const AnimationCurveTpl<float>& curve);
+template void AddCurveToDenseClip<Vector3f>(mecanim::animation::DenseClip& clip, int curveIndex, const AnimationCurveTpl<Vector3f>& curve);
+template void AddCurveToDenseClip<Quaternionf>(mecanim::animation::DenseClip& clip, int curveIndex, const AnimationCurveTpl<Quaternionf>& curve);
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+typedef AnimationCurveVec3::Keyframe KeyframeVec3;
+static Vector3f Evaluate3 (const mecanim::animation::DenseClip& clip, float time)
+{
+ float output[4];
+ SampleClip(clip, time, output);
+
+ return Vector3f(output[1], output[2], output[3]);
+}
+
+typedef AnimationCurve::Keyframe Keyframe;
+static float Evaluate1 (const mecanim::animation::DenseClip& clip, float time)
+{
+ float output;
+ output = SampleClipAtIndex(clip, time, 0);
+ return output;
+}
+
+
+SUITE (DenseClipBuilderTests)
+{
+TEST (DenseClipBuilder_EvaluationVector3)
+{
+ mecanim::memory::MecanimAllocator alloc(kMemTempAlloc);
+
+ AnimationCurveVec3 curve;
+ curve.AddKeyBackFast(KeyframeVec3(0.5F, Vector3f(0.0,1.0,2.0)));
+ curve.AddKeyBackFast(KeyframeVec3(1.0F, Vector3f(3.0,0.0,4.0)));
+ curve.AddKeyBackFast(KeyframeVec3(2.0F, Vector3f(0.0,-1.0,-2.0)));
+
+ AnimationCurve curve2;
+ curve2.AddKeyBackFast(Keyframe(0.3F, 1.0));
+ curve2.AddKeyBackFast(Keyframe(1.2F, 20.0));
+ curve2.AddKeyBackFast(Keyframe(2.3F, 5.0));
+
+
+ mecanim::animation::DenseClip clip;
+ CreateDenseClip (clip, 3+1, std::min(curve.GetRange().first, curve2.GetRange().first), std::max(curve.GetRange().second, curve2.GetRange().second), 30.0F, alloc);
+ AddCurveToDenseClip(clip, 0, curve2);
+ AddCurveToDenseClip(clip, 1, curve);
+
+ float kEpsilon = 0.001F;
+
+ CHECK(CompareApproximately(curve.EvaluateClamp(1.5F), Evaluate3(clip, 1.5F), kEpsilon));
+
+
+ CHECK(CompareApproximately(curve.EvaluateClamp(-5.0), Evaluate3(clip, -5.0F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(-5.0), Evaluate3(clip, -5.0F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(0.0F), Evaluate3(clip, 0.0F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(1.439F), Evaluate3(clip, 1.439F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(2.0F), Evaluate3(clip, 2.0F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(0.1F), Evaluate3(clip, 0.1F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(100.0F), Evaluate3(clip, 100.0F), kEpsilon));
+ CHECK(CompareApproximately(curve.EvaluateClamp(-19), Evaluate3(clip, -19.0F), kEpsilon));
+
+ DestroyDenseClip (clip, alloc);
+}
+}
+#endif
diff --git a/Runtime/Animation/DenseClipBuilder.h b/Runtime/Animation/DenseClipBuilder.h
new file mode 100644
index 0000000..dff9864
--- /dev/null
+++ b/Runtime/Animation/DenseClipBuilder.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Runtime/Math/AnimationCurve.h"
+#include "Runtime/mecanim/animation/denseclip.h"
+
+void CreateDenseClip(mecanim::animation::DenseClip& clip, UInt32 curveCount, float begin, float end, float sampleRate, mecanim::memory::Allocator& alloc);
+
+template<class T>
+void AddCurveToDenseClip(mecanim::animation::DenseClip& clip, int curveIndex, const AnimationCurveTpl<T>& curve);
diff --git a/Runtime/Animation/EditorCurveBinding.h b/Runtime/Animation/EditorCurveBinding.h
new file mode 100644
index 0000000..c98ec7e
--- /dev/null
+++ b/Runtime/Animation/EditorCurveBinding.h
@@ -0,0 +1,34 @@
+#pragma once
+
+class MonoScript;
+
+struct EditorCurveBinding
+{
+ std::string path;
+ std::string attribute; ///@TODO: Rename to propertyName, thats how it is called in C#
+ int classID;
+ MonoScript* script;
+ bool isPPtrCurve;
+
+ EditorCurveBinding ()
+ {
+ script = NULL;
+ isPPtrCurve = false;
+ classID = 0;
+ }
+
+
+ EditorCurveBinding (const std::string& inPath, int inClassID, MonoScript* inScript, const std::string& inAttribute, bool inIsPPtrCurve)
+ {
+ path = inPath;
+ classID = inClassID;
+ script = inScript;
+ attribute = inAttribute;
+ isPPtrCurve = inIsPPtrCurve;
+ }
+
+ friend bool operator == (const EditorCurveBinding& lhs, const EditorCurveBinding& rhs)
+ {
+ return lhs.path == rhs.path && lhs.attribute == rhs.attribute && lhs.classID == rhs.classID && lhs.script == rhs.script && lhs.isPPtrCurve == rhs.isPPtrCurve;
+ }
+};
diff --git a/Runtime/Animation/GenericAnimationBindingCache.cpp b/Runtime/Animation/GenericAnimationBindingCache.cpp
new file mode 100644
index 0000000..fe6688f
--- /dev/null
+++ b/Runtime/Animation/GenericAnimationBindingCache.cpp
@@ -0,0 +1,839 @@
+#include "UnityPrefix.h"
+#include "GenericAnimationBindingCache.h"
+#include "AnimationClipBindings.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/mecanim/memory.h"
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/mecanim/animation/clipmuscle.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Interfaces/IAnimationBinding.h"
+#include "Animator.h"
+#include "Runtime/Utilities/InitializeAndCleanup.h"
+#include "AnimatorController.h"
+#include "Runtime/Core/Callbacks/GlobalCallbacks.h"
+
+const static char* kIsActive = "m_IsActive";
+
+typedef UInt32 BindingHash;
+
+namespace UnityEngine
+{
+namespace Animation
+{
+
+bool IsMuscleBinding(const GenericBinding& binding)
+{
+ return binding.classID == ClassID(Animator) && binding.customType == kBindMuscle;
+}
+
+struct CachedBinding
+{
+ BindingHash propertyHash;
+ int offset;
+ int bindType;
+};
+
+struct CachedComponentBindings
+{
+ ScriptingClassPtr scriptingClass;
+ int classID;
+ size_t bindingSize;
+ CachedBinding* bindings;
+};
+
+
+static GenericAnimationBindingCache* gGenericBindingCache = NULL;
+
+static CachedComponentBindings* GenerateComponentBinding (int classID, ScriptingObjectPtr scriptingInstance, ScriptingClassPtr scriptingClass, Object* targetObject);
+static const CachedBinding* FindBinding (const CachedComponentBindings& componentBinding, BindingHash attribute);
+static void DestroyBinding (CachedComponentBindings* binding);
+
+bool operator < (const CachedBinding& lhs, const CachedBinding& rhs)
+{
+ return lhs.propertyHash < rhs.propertyHash;
+}
+
+
+static ClassIDType BindCurve (const CachedComponentBindings& cachedBinding, const GenericBinding& inputBinding, Object* targetObject, void* targetPtr, BoundCurve& bound)
+{
+ const CachedBinding* found = FindBinding (cachedBinding, inputBinding.attribute);
+ if (found == NULL)
+ {
+ bound.targetType = kUnbound;
+ return ClassID(Undefined);
+ }
+
+ bound.targetObject = targetObject;
+ bound.targetPtr = reinterpret_cast<UInt8*> (targetPtr) + found->offset;
+ bound.targetType = found->bindType;
+
+ if (found->bindType == kBindFloatToBool)
+ return ClassID(bool);
+ else
+ return ClassID(float);
+}
+
+static int GetTypeTreeBindType (const TypeTree& variable)
+{
+ if (variable.m_MetaFlag & kDontAnimate)
+ return kUnbound;
+
+ if (variable.m_Type == "float")
+ return kBindFloat;
+ else if (variable.m_Type == "bool" || (variable.m_Type == "UInt8" && (variable.m_MetaFlag & kEditorDisplaysCheckBoxMask)))
+ return kBindFloatToBool;
+ else
+ return kUnbound;
+}
+
+static int GetAnimatablePropertyOffset (const TypeTree* variable, ScriptingObjectPtr scriptingInstance)
+{
+ if (variable && scriptingInstance)
+ {
+#if ENABLE_MONO
+ if (scriptingInstance)
+ {
+ UInt32 offset = reinterpret_cast<UInt8*> (variable->m_DirectPtr) - reinterpret_cast<UInt8*> (scriptingInstance);
+ UInt32 size = mono_class_instance_size(mono_object_get_class(scriptingInstance));
+ if (offset < size)
+ return offset;
+ }
+#endif
+ return -1;
+ }
+ else if (variable)
+ return variable->m_ByteOffset;
+ else
+ return -1;
+}
+
+static void GetGenericAnimatablePropertiesRecurse (const TypeTree& typeTree, std::string& path, ScriptingObjectPtr scriptingInstance, const EditorCurveBinding& baseBinding, std::vector<EditorCurveBinding>& outProperties)
+{
+ size_t previousSize = path.size();
+ if (!path.empty())
+ path += '.';
+ path += typeTree.m_Name;
+
+ int offset = GetAnimatablePropertyOffset (&typeTree, scriptingInstance);
+ if (offset != -1)
+ {
+ int bindType = GetTypeTreeBindType (typeTree);
+ if (bindType != kUnbound)
+ {
+ outProperties.push_back(baseBinding);
+ outProperties.back().attribute = path;
+ }
+ }
+
+ for (TypeTree::const_iterator i=typeTree.begin();i != typeTree.end();++i)
+ {
+ GetGenericAnimatablePropertiesRecurse (*i, path, scriptingInstance, baseBinding, outProperties);
+ }
+
+ path.resize(previousSize);
+}
+
+static void GetGenericAnimatableProperties (int classID, Object& targetObject, std::vector<EditorCurveBinding>& outProperties)
+{
+ //@TODO: Use temp TypeTree mem?
+ TypeTree typeTree;
+ GenerateTypeTree (targetObject, &typeTree);
+
+ EditorCurveBinding baseBinding;
+ baseBinding.classID = classID;
+
+ MonoBehaviour* behaviour = dynamic_pptr_cast<MonoBehaviour*> (&targetObject);
+ ScriptingObjectPtr scriptingInstance = SCRIPTING_NULL;;
+ if (behaviour)
+ {
+ scriptingInstance = behaviour->GetInstance();
+ baseBinding.script = behaviour->GetScript();
+ }
+
+ // Create cached bindings (We don't want to include the root name, so iterate over the children directly)
+ std::string path;
+ for (TypeTree::const_iterator i=typeTree.begin();i != typeTree.end();++i)
+ GetGenericAnimatablePropertiesRecurse (*i, path, scriptingInstance, baseBinding, outProperties);
+}
+
+static void GenerateBindingRecurse (const TypeTree& typeTree, ScriptingObjectPtr scriptingInstance, mecanim::crc32 attributeHash, dynamic_array<CachedBinding>& bindings)
+{
+ // Update hash recursively
+ if (attributeHash.checksum() != 0)
+ attributeHash.process_bytes(".", 1);
+ attributeHash.process_bytes(typeTree.m_Name.c_str(), strlen(typeTree.m_Name.c_str()));
+
+ int offset = GetAnimatablePropertyOffset (&typeTree, scriptingInstance);
+ if (offset != -1)
+ {
+ int bindType = GetTypeTreeBindType (typeTree);
+ if (bindType != kUnbound)
+ {
+ CachedBinding& binding = bindings.push_back();
+ binding.propertyHash = attributeHash.checksum();
+ binding.offset = offset;
+ binding.bindType = bindType;
+ }
+ }
+
+ for (TypeTree::const_iterator i=typeTree.begin();i != typeTree.end();++i)
+ GenerateBindingRecurse (*i, scriptingInstance, attributeHash, bindings);
+}
+
+
+template<class T>
+bool has_duplicate_sorted (const T* begin, size_t count)
+{
+ if (count == 0)
+ return false;
+
+ const T* previous = begin;
+ const T* i = begin;
+ const T* end = begin + count;
+ i++;
+ for (; i != end;++i)
+ {
+ if (!(*previous < *i))
+ return true;
+
+ previous = i;
+ }
+
+ return false;
+}
+
+// Find a value in a sorted array
+// Returns NULL if there is no value in the array
+template<class T>
+const T* find_binary_search (const T* begin, size_t count, const T& value)
+{
+ const T* found = std::lower_bound (begin, begin + count, value);
+ if (found == begin + count || value < *found)
+ return NULL;
+ else
+ return found;
+}
+
+
+static const CachedBinding* FindBinding (const CachedComponentBindings& componentBinding, BindingHash attribute)
+{
+ CachedBinding proxy;
+ proxy.propertyHash = attribute;
+
+ return find_binary_search(componentBinding.bindings, componentBinding.bindingSize, proxy);
+}
+
+static void DestroyBinding (CachedComponentBindings* binding)
+{
+ UNITY_FREE(kMemAnimation, binding);
+}
+
+
+static CachedComponentBindings* GenerateComponentBinding (int classID, ScriptingObjectPtr scriptingInstance, ScriptingClassPtr scriptingClass, Object* targetObject)
+{
+ //@TODO: Use temp TypeTree mem?
+ TypeTree typeTree;
+ GenerateTypeTree (*targetObject, &typeTree);
+
+ dynamic_array<CachedBinding> bindings (kMemTempAlloc);
+
+ // Create cached bindings (We don't want to include the root name, so iterate over the children directly)
+ for (TypeTree::const_iterator i=typeTree.begin();i != typeTree.end();++i)
+ GenerateBindingRecurse (*i, scriptingInstance, mecanim::crc32(), bindings);
+
+ std::sort (bindings.begin(), bindings.end());
+
+#if DEBUGMODE
+ if (has_duplicate_sorted (bindings.begin(), bindings.size()))
+ {
+ ///@TODO: make a nice fullclassname...
+
+
+ WarningString(Format("Animation bindings for %s are not unique. Some properties might get bound incorrectly.", targetObject->GetClassName().c_str()));
+ }
+#endif
+
+ size_t size = sizeof (CachedComponentBindings) + sizeof (CachedBinding) * bindings.size();
+ mecanim::memory::InPlaceAllocator allocator (UNITY_MALLOC(kMemAnimation, size), size);
+
+ CachedComponentBindings* binding = allocator.Construct<CachedComponentBindings> ();
+
+ binding->classID = classID;
+ binding->scriptingClass = scriptingClass;
+ binding->bindingSize = bindings.size();
+ binding->bindings = allocator.ConstructArray<CachedBinding> (bindings.begin(), bindings.size());
+
+ return binding;
+}
+
+void GenericAnimationBindingCache::DidReloadDomain ()
+{
+ if (gGenericBindingCache != NULL)
+ Clear(gGenericBindingCache->m_Scripts);
+}
+
+void InitializeGenericAnimationBindingCache ()
+{
+ mecanim::crc32::crc32_table_type::init_table();
+
+ gGenericBindingCache = UNITY_NEW_AS_ROOT(GenericAnimationBindingCache, kMemAnimation, "AnimationBindingCache", "");
+
+ GlobalCallbacks::Get().didReloadMonoDomain.Register(GenericAnimationBindingCache::DidReloadDomain);
+}
+
+void CleanupGenericAnimationBindingCache ()
+{
+ UNITY_DELETE(gGenericBindingCache, kMemAnimation);
+
+ GlobalCallbacks::Get().didReloadMonoDomain.Unregister(GenericAnimationBindingCache::DidReloadDomain);
+}
+
+static RegisterRuntimeInitializeAndCleanup s_RegisterBindingCache (InitializeGenericAnimationBindingCache, CleanupGenericAnimationBindingCache);
+
+GenericAnimationBindingCache& GetGenericAnimationBindingCache ()
+{
+ return *gGenericBindingCache;
+}
+
+GenericAnimationBindingCache::GenericAnimationBindingCache ()
+{
+ m_IsActiveHash = mecanim::processCRC32 (kIsActive);
+ m_Classes.resize_initialized (kLargestRuntimeClassID, NULL);
+ m_CustomBindingInterfaces.resize_initialized (kAllBindingCount, NULL);
+}
+
+GenericAnimationBindingCache::~GenericAnimationBindingCache ()
+{
+ Clear(m_Classes);
+ Clear(m_Scripts);
+}
+
+
+void CreateTransformBinding (const UnityStr& path, int bindType, GenericBinding& outputBinding)
+{
+ outputBinding.path = mecanim::processCRC32(path.c_str());
+ outputBinding.attribute = bindType;
+ outputBinding.classID = ClassID(Transform);
+ outputBinding.customType = kUnbound;
+ outputBinding.isPPtrCurve = false;
+ outputBinding.script = NULL;
+}
+
+void GenericAnimationBindingCache::CreateGenericBinding (const UnityStr& path, int classID, PPtr<MonoScript> script, const UnityStr& attribute, bool pptrCurve, GenericBinding& outputBinding) const
+{
+ outputBinding.path = mecanim::processCRC32(path.c_str());
+ outputBinding.attribute = mecanim::processCRC32(attribute.c_str());
+ outputBinding.classID = classID;
+ outputBinding.customType = kUnbound;
+ outputBinding.isPPtrCurve = pptrCurve;
+ outputBinding.script = script;
+
+ // Pre-bind muscle indices
+ //@TODO: is this really worth doing? Maybe we should just make it more consistent?
+ if (!pptrCurve)
+ {
+ if (classID == ClassID(Animator))
+ {
+ mecanim::int32_t muscleIndex = mecanim::animation::FindMuscleIndex(outputBinding.attribute);
+ if (muscleIndex != -1)
+ {
+ outputBinding.attribute = muscleIndex;
+ outputBinding.customType = kBindMuscle;
+ return;
+ }
+ }
+ }
+
+ // Search custom bindings
+ for (int i=0;i<m_CustomBindings.size();i++)
+ {
+ int customBindingType = m_CustomBindings[i].customBindingType;
+ const IAnimationBinding* bindingInterface = m_CustomBindingInterfaces[customBindingType];
+ if (Object::IsDerivedFromClassID (classID, m_CustomBindings[i].classID) && bindingInterface->GenerateBinding (attribute, pptrCurve, outputBinding))
+ {
+ outputBinding.customType = customBindingType;
+ return;
+ }
+ }
+}
+
+static inline MonoBehaviour* GetComponentWithScript (Transform& transform, PPtr<Object> target)
+{
+ MonoScript* script = dynamic_instanceID_cast<MonoScript*> (target.GetInstanceID());
+ return static_cast<MonoBehaviour*> (GetComponentWithScript (transform.GetGameObject(), ClassID(MonoBehaviour), script));
+}
+
+#if ENABLE_MONO
+
+ClassIDType GenericAnimationBindingCache::BindScript (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound)
+{
+ MonoBehaviour* behaviour = GetComponentWithScript (transform, inputBinding.script);
+
+ ScriptingObjectPtr instance = behaviour ? behaviour->GetInstance() : SCRIPTING_NULL;
+ // No valid instance -> no bound curve
+ if (instance == NULL)
+ {
+ bound.targetType = kUnbound;
+ return ClassID(Undefined);
+ }
+
+ ScriptingClassPtr klass = behaviour->GetClass();
+
+ // Find cached binding stored by ScriptingClassPtr
+ CachedComponentBindings* bindings = NULL;
+ for (int i=0;i<m_Scripts.size();i++)
+ {
+ if (m_Scripts[i]->scriptingClass == klass)
+ {
+ bindings = m_Scripts[i];
+ break;
+ }
+ }
+
+ // Create a new binding for this ScriptingClassPtr
+ if (bindings == NULL)
+ {
+ bindings = GenerateComponentBinding(inputBinding.classID, instance, klass, behaviour);
+ m_Scripts.push_back(bindings);
+ }
+
+ // Bind the specific curve
+ return BindCurve (*bindings, inputBinding, behaviour, instance, bound);
+}
+#endif
+
+ClassIDType GenericAnimationBindingCache::BindGenericComponent (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound)
+{
+ Unity::Component* target = transform.GetGameObject().QueryComponentT<Unity::Component>(inputBinding.classID);
+ if (target == NULL)
+ return ClassID(Undefined);
+
+ if (m_Classes[inputBinding.classID] == NULL)
+ m_Classes[inputBinding.classID] = GenerateComponentBinding(inputBinding.classID, SCRIPTING_NULL, SCRIPTING_NULL, target);
+
+ return BindCurve (*m_Classes[inputBinding.classID], inputBinding, target, target, bound);
+}
+
+Object* FindAnimatedObject (Unity::GameObject& root, const EditorCurveBinding& inputBinding)
+{
+ Transform* transform = FindRelativeTransformWithPath (root.GetComponent(Transform), inputBinding.path.c_str());
+ if (transform == NULL)
+ return NULL;
+
+ if (inputBinding.classID == ClassID(GameObject))
+ {
+ return &transform->GetGameObject();
+ }
+ else if (inputBinding.classID == ClassID(MonoBehaviour))
+ {
+ return GetComponentWithScript (*transform, inputBinding.script);
+ }
+ else
+ {
+ return transform->GetGameObject().QueryComponentT<Unity::Component>(inputBinding.classID);
+ }
+}
+
+ClassIDType GenericAnimationBindingCache::BindPPtrGeneric (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound)
+{
+ if (inputBinding.customType == kUnbound)
+ return ClassID(Undefined);
+
+ return BindCustom (inputBinding, transform, bound);
+}
+
+ClassIDType GenericAnimationBindingCache::BindCustom (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound) const
+{
+ Assert(inputBinding.customType != kUnbound);
+
+ Unity::Component* target;
+ if (inputBinding.classID == ClassID(MonoBehaviour))
+ target = GetComponentWithScript (transform, inputBinding.script);
+ else
+ target = transform.GetGameObject().QueryComponentT<Unity::Component>(inputBinding.classID);
+
+ IAnimationBinding* customBinding = m_CustomBindingInterfaces[inputBinding.customType];
+ if (customBinding != NULL && target != NULL)
+ {
+ BoundCurve tempBound;
+ tempBound.targetType = inputBinding.customType;
+ tempBound.customBinding = customBinding;
+ tempBound.targetObject = target;
+
+ ClassIDType type = customBinding->BindValue (*target, inputBinding, tempBound);
+ if (type != ClassID (Undefined))
+ bound = tempBound;
+
+ return type;
+ }
+
+ return ClassID(Undefined);
+}
+
+
+ClassIDType GenericAnimationBindingCache::BindGeneric (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound)
+{
+ // Bind game object active state
+ if (inputBinding.classID == ClassID(GameObject))
+ {
+ if (inputBinding.attribute == m_IsActiveHash && inputBinding.path != 0)
+ {
+ bound.targetPtr = NULL;
+ bound.targetType = kBindGameObjectActive;
+ bound.targetObject = transform.GetGameObjectPtr();
+ return ClassID(bool);
+ }
+
+ return ClassID(Undefined);
+ }
+ // Custom animator bindings
+ else if (inputBinding.classID == ClassID(Animator))
+ {
+ // We bind these in mecanim internally.
+ return ClassID(float);
+ }
+ // Custom binding
+ else if (inputBinding.customType != kUnbound)
+ return BindCustom (inputBinding, transform, bound);
+ // Script bindings
+ else if (inputBinding.classID == ClassID(MonoBehaviour))
+ {
+#if ENABLE_MONO
+ return BindScript(inputBinding, transform, bound);
+#else
+ return ClassID(Undefined);
+#endif
+ }
+ // Generic bindings
+ else
+ {
+ return BindGenericComponent(inputBinding, transform, bound);
+ }
+}
+
+void GenericAnimationBindingCache::RegisterIAnimationBinding (ClassIDType classID, int customBindingType, IAnimationBinding* customBinding)
+{
+ CustomBinding bind;
+ bind.classID = classID;
+ bind.customBindingType = customBindingType;
+ m_CustomBindings.push_back(bind);
+
+ Assert(m_CustomBindingInterfaces[customBindingType] == NULL);
+ m_CustomBindingInterfaces[customBindingType] = customBinding;
+}
+
+std::string GenericAnimationBindingCache::SerializedPropertyPathToCurveAttribute (Object& target, const char* propertyPath) const
+{
+ ClassIDType classID = target.GetClassID();
+ // Search custom bindings
+ for (int i=0;i<m_CustomBindings.size();i++)
+ {
+ if (!Object::IsDerivedFromClassID (classID, m_CustomBindings[i].classID))
+ continue;
+
+ int customBindingType = m_CustomBindings[i].customBindingType;
+ const IAnimationBinding* customBinding = m_CustomBindingInterfaces[customBindingType];
+
+ string attributeName = customBinding->SerializedPropertyPathToCurveAttribute (target, propertyPath);
+ if (!attributeName.empty())
+ return attributeName;
+ }
+
+ return string();
+}
+
+std::string GenericAnimationBindingCache::CurveAttributeToSerializedPath (Unity::GameObject& root, const EditorCurveBinding& binding) const
+{
+ Transform* transform = FindRelativeTransformWithPath (root.GetComponent(Transform), binding.path.c_str());
+ if (transform == NULL)
+ return std::string();
+
+ GenericBinding genericBinding;
+ CreateGenericBinding (binding.path, binding.classID, binding.script, binding.attribute, binding.isPPtrCurve, genericBinding);
+
+ if (genericBinding.customType == kUnbound)
+ return std::string();
+
+ BoundCurve bound;
+ if (BindCustom (genericBinding, *transform, bound) == ClassID(Undefined))
+ return std::string();
+
+ if (bound.customBinding != NULL)
+ return bound.customBinding->CurveAttributeToSerializedPath (bound);
+ else
+ return string();
+}
+
+void GenericAnimationBindingCache::Clear (CachedComponentBindingArray& array)
+{
+ for (int i=0;i<array.size();i++)
+ UNITY_FREE(kMemAnimation, array[i]);
+ array.clear();
+}
+
+int GetBoundCurveIntValue (const BoundCurve& bind)
+{
+ return bind.customBinding->GetPPtrValue (bind);
+}
+
+void SetBoundCurveIntValue (const BoundCurve& bind, int value)
+{
+ bind.customBinding->SetPPtrValue (bind, value);
+}
+
+void SetBoundCurveFloatValue (const BoundCurve& bind, float value)
+{
+ UInt32 targetType = bind.targetType;
+ Assert(bind.targetType != kUnbound && bind.targetType != kBindTransformRotation && bind.targetType != kBindTransformPosition && bind.targetType != kBindTransformScale);
+
+ if ( targetType == kBindFloat )
+ {
+ *reinterpret_cast<float*>(bind.targetPtr) = value;
+ }
+ else if ( targetType == kBindFloatToBool )
+ {
+ *reinterpret_cast<UInt8*>(bind.targetPtr) = AnimationFloatToBool(value);
+ }
+ else if ( targetType == kBindGameObjectActive)
+ {
+ GameObject* go = static_cast<GameObject*> (bind.targetObject);
+ go->SetSelfActive (AnimationFloatToBool(value));
+ }
+ else
+ bind.customBinding->SetFloatValue (bind, value);
+}
+
+float GetBoundCurveFloatValue (const BoundCurve& bind)
+{
+ UInt32 targetType = bind.targetType;
+ Assert(bind.targetType >= kMinSinglePropertyBinding);
+
+ if ( targetType == kBindFloat )
+ {
+ return *reinterpret_cast<float*>(bind.targetPtr);
+ }
+ else if ( targetType == kBindFloatToBool )
+ {
+ return AnimationBoolToFloat(*reinterpret_cast<UInt8*>(bind.targetPtr));
+ }
+ else if ( targetType == kBindGameObjectActive )
+ {
+ GameObject* go = static_cast<GameObject*> (bind.targetObject);
+ return AnimationBoolToFloat (go->IsSelfActive ());
+ }
+ else
+ return bind.customBinding->GetFloatValue (bind);
+}
+
+
+bool ShouldAwakeGeneric (const BoundCurve& bind)
+{
+ return bind.targetType == kBindFloat || bind.targetType == kBindFloatToBool;
+}
+
+void BoundCurveValueAwakeGeneric (Object& targetObject)
+{
+ targetObject.AwakeFromLoad(kDefaultAwakeFromLoad);
+ targetObject.SetDirty();
+}
+
+#if UNITY_EDITOR
+
+static void ExtractGameObjectIsActiveBindings (std::vector<EditorCurveBinding>& outProperties)
+{
+ AddBinding (outProperties, ClassID(GameObject), kIsActive);
+}
+
+static void ExtractAllAnimatorBindings (Unity::Component& targetObject, vector<EditorCurveBinding>& attributes)
+{
+ Animator& animator = static_cast<Animator&> (targetObject);
+ if (animator.IsHuman())
+ {
+ for(int curveIter = 0; curveIter < mecanim::animation::s_ClipMuscleCurveCount; curveIter++)
+ AddBinding (attributes, ClassID(Animator), mecanim::animation::GetMuscleCurveName(curveIter).c_str());
+ }
+
+ if(animator.GetAnimatorController())
+ {
+ int eventCount = animator.GetAnimatorController()->GetParameterCount();
+
+ for(int eventIter = 0; eventIter < eventCount; eventIter++)
+ {
+ AnimatorControllerParameter* parameter = animator.GetAnimatorController()->GetParameter(eventIter);
+ if (parameter->GetType() == 1)
+ AddBinding (attributes, ClassID(Animator), parameter->GetName());
+ }
+ }
+}
+
+static void ProcessRelativePath (Unity::GameObject& gameObject, Unity::GameObject& root, std::vector<EditorCurveBinding>& outProperties)
+{
+ string path = CalculateTransformPath (gameObject.GetComponent(Transform), root.QueryComponent(Transform));
+ for (int i=0;i<outProperties.size();i++)
+ outProperties[i].path = path;
+}
+
+void GenericAnimationBindingCache::GetAllAnimatableProperties (Unity::GameObject& go, Unity::GameObject& root, std::vector<EditorCurveBinding>& outProperties)
+{
+ Assert(outProperties.empty());
+
+ if (&go != &root)
+ ExtractGameObjectIsActiveBindings(outProperties);
+
+ for (int i=0;i<go.GetComponentCount ();i++)
+ {
+ Unity::Component& com = go.GetComponentAtIndex (i);
+ int classID = com.GetClassID();
+
+ // Search custom bindings
+ for (int c=0;c<m_CustomBindings.size();c++)
+ {
+ int customBindingType = m_CustomBindings[c].customBindingType;
+ if (Object::IsDerivedFromClassID (classID, m_CustomBindings[c].classID))
+ m_CustomBindingInterfaces[customBindingType]->GetAllAnimatableProperties (com, outProperties);
+ }
+
+ if (classID == ClassID(Animator))
+ ExtractAllAnimatorBindings (com, outProperties);
+
+ GetGenericAnimatableProperties (classID, go.GetComponentAtIndex(i), outProperties);
+ }
+
+ ProcessRelativePath (go, root, outProperties);
+}
+
+static bool GetFloatValueAnimatorBinding (Transform& transform, const EditorCurveBinding& binding, float* value)
+{
+ Animator* animator = transform.QueryComponent(Animator);
+
+ if (animator == NULL)
+ return false;
+
+ BindingHash hash = mecanim::processCRC32 (binding.attribute.c_str());
+
+ if(animator->GetMuscleValue(hash,value))
+ return true;
+
+ GetSetValueResult result = animator->GetFloat (hash, *value);
+ return result == kGetSetSuccess;
+}
+
+static bool GetFloatValueTransformBinding (Transform& transform, const EditorCurveBinding& binding, float* value)
+{
+ int axis = -1;
+ char lastCharacter = binding.attribute[binding.attribute.size()-1];
+ if (lastCharacter == 'w')
+ axis = 3;
+ else if (lastCharacter >= 'x' && lastCharacter <= 'z')
+ axis = lastCharacter - 'x';
+ else
+ return false;
+
+ if (BeginsWith(binding.attribute, "m_LocalPosition") && axis < 3)
+ {
+ *value = transform.GetLocalPosition()[axis];
+ return true;
+ }
+ else if (BeginsWith(binding.attribute, "m_LocalScale") && axis < 3)
+ {
+ *value = transform.GetLocalScale()[axis];
+ return true;
+ }
+ else if (BeginsWith(binding.attribute, "m_LocalRotation") && axis < 4)
+ {
+ *value = transform.GetLocalRotation()[axis];
+ return true;
+ }
+ else if (BeginsWith(binding.attribute, "localEulerAngles") && axis < 3)
+ {
+ *value = transform.GetLocalEulerAngles()[axis];
+ return true;
+ }
+ else
+ return false;
+}
+
+ClassIDType GetFloatValue (Unity::GameObject& root, const EditorCurveBinding& binding, float* value)
+{
+ *value = 0.0F;
+
+ if (binding.isPPtrCurve)
+ return ClassID(Undefined);
+
+ Transform* transform = FindRelativeTransformWithPath (root.GetComponent(Transform), binding.path.c_str());
+ if (transform == NULL)
+ return ClassID(Undefined);
+
+ // Transform has a special codepath for setting T/R/S as Vector3
+ if (binding.classID == ClassID(Transform))
+ {
+ if (GetFloatValueTransformBinding (*transform, binding, value))
+ return ClassID(float);
+ }
+ // Animator bindings are handled through a special more integrated code path in mecanim
+ else if (binding.classID == ClassID(Animator))
+ {
+ if (GetFloatValueAnimatorBinding (*transform, binding, value))
+ return ClassID(float);
+ }
+ else
+ {
+ GenericBinding genericBinding;
+ GetGenericAnimationBindingCache().CreateGenericBinding (binding.path, binding.classID, binding.script, binding.attribute, binding.isPPtrCurve, genericBinding);
+
+ BoundCurve bound;
+ ClassIDType bindType = GetGenericAnimationBindingCache().BindGeneric (genericBinding, *transform, bound);
+ if (bound.targetType == kUnbound)
+ return ClassID(Undefined);
+
+ *value = GetBoundCurveFloatValue (bound);
+
+ return bindType;
+ }
+
+ return ClassID(Undefined);
+}
+
+ClassIDType GetPPtrValue (Unity::GameObject& root, const EditorCurveBinding& binding, int* instanceID)
+{
+ *instanceID = 0;
+
+ if (!binding.isPPtrCurve)
+ return ClassID(Undefined);
+
+ Transform* transform = FindRelativeTransformWithPath (root.GetComponent(Transform), binding.path.c_str());
+ if (transform == NULL)
+ return ClassID(Undefined);
+
+ GenericBinding genericBinding;
+ GetGenericAnimationBindingCache().CreateGenericBinding (binding.path, binding.classID, binding.script, binding.attribute, binding.isPPtrCurve, genericBinding);
+
+ BoundCurve bound;
+ ClassIDType boundClassID = GetGenericAnimationBindingCache().BindPPtrGeneric (genericBinding, *transform, bound);
+ if (bound.targetType == kUnbound)
+ return ClassID(Undefined);
+
+ *instanceID = GetBoundCurveIntValue (bound);
+ return boundClassID;
+}
+
+ClassIDType GetEditorCurveValueClassID (Unity::GameObject& root, const EditorCurveBinding& binding)
+{
+ // Try if it's a PPtr curve
+ int tempInstanceID;
+ ClassIDType boundClassID = GetPPtrValue (root, binding, &tempInstanceID);
+ if (boundClassID != ClassID(Undefined))
+ return boundClassID;
+
+ // Otherwise find the type of float curve it is
+ float tempFloat;
+ return GetFloatValue (root, binding, &tempFloat);
+}
+
+#endif
+
+}
+}
diff --git a/Runtime/Animation/GenericAnimationBindingCache.h b/Runtime/Animation/GenericAnimationBindingCache.h
new file mode 100644
index 0000000..38fba09
--- /dev/null
+++ b/Runtime/Animation/GenericAnimationBindingCache.h
@@ -0,0 +1,118 @@
+#pragma once
+
+#include "Runtime/Utilities/dynamic_array.h"
+#include "EditorCurveBinding.h"
+#include "Runtime/BaseClasses/ClassIDs.h"
+#include "BoundCurve.h"
+
+typedef UInt32 BindingHash;
+
+namespace Unity { class GameObject; }
+class Transform;
+class Object;
+class MonoScript;
+template<class T> class PPtr;
+
+class IAnimationBinding;
+
+namespace UnityEngine
+{
+namespace Animation
+{
+
+ struct CachedComponentBindings;
+ struct GenericBinding;
+
+ bool IsMuscleBinding (const GenericBinding& binding);
+
+ inline bool AnimationFloatToBool (float result)
+ {
+ //@TODO: Maybe we should change the behaviour to be that > 0.01F means enabled instead of the close to zero logic....
+ return result > 0.001F || result < -0.001F;
+ }
+
+ inline float AnimationBoolToFloat (bool value)
+ {
+ return value ? 1.0F : 0.0F;
+ }
+
+ class GenericAnimationBindingCache
+ {
+ public:
+
+ GenericAnimationBindingCache ();
+ ~GenericAnimationBindingCache ();
+
+ ClassIDType BindGeneric (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound);
+ ClassIDType BindPPtrGeneric (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound);
+
+ void CreateGenericBinding (const UnityStr& path, int classID, PPtr<MonoScript> script, const UnityStr& attribute, bool pptrCurve, GenericBinding& outputBinding) const;
+
+ static void DidReloadDomain ();
+
+ void RegisterIAnimationBinding (ClassIDType classID, int inCustomType, IAnimationBinding* bindingInterface);
+
+ // Editor API
+ void GetAnimatableProperties (Transform& transform, int classID, const PPtr<MonoScript>& script, std::vector<EditorCurveBinding>& outProperties);
+ void GetAllAnimatableProperties (Unity::GameObject& gameObject, Unity::GameObject& root, std::vector<EditorCurveBinding>& outProperties);
+
+ std::string SerializedPropertyPathToCurveAttribute (Object& target, const char* propertyPath) const;
+ std::string CurveAttributeToSerializedPath (Unity::GameObject& root, const EditorCurveBinding& binding) const;
+
+ private:
+
+ typedef dynamic_array<CachedComponentBindings*> CachedComponentBindingArray;
+
+ ClassIDType BindGenericComponent (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound);
+ ClassIDType BindScript (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound);
+ ClassIDType BindCustom (const GenericBinding& inputBinding, Transform& transform, BoundCurve& bound) const;
+
+ static void Clear (CachedComponentBindingArray& array);
+
+ struct CustomBinding
+ {
+ int classID;
+ int customBindingType;
+ };
+
+ dynamic_array<CustomBinding> m_CustomBindings;
+ dynamic_array<IAnimationBinding*> m_CustomBindingInterfaces;
+
+ CachedComponentBindingArray m_Classes;
+ CachedComponentBindingArray m_Scripts;
+ BindingHash m_IsActiveHash;
+ };
+
+ GenericAnimationBindingCache& GetGenericAnimationBindingCache ();
+
+ void InitializeGenericAnimationBindingCache ();
+ void CleanupGenericAnimationBindingCache ();
+
+ // Runtime API
+ // NOt sure if i like this???
+// void CreateGenericBinding (const UnityStr& path, int classID, PPtr<MonoScript> script, const UnityStr& attribute, bool pptrCurve, GenericBinding& outputBinding);
+ void CreateTransformBinding (const UnityStr& path, int bindType, GenericBinding& outputBinding);
+
+ float GetBoundCurveFloatValue (const BoundCurve& bind);
+ void SetBoundCurveFloatValue (const BoundCurve& bind, float value);
+
+ void SetBoundCurveIntValue (const BoundCurve& bind, int value);
+ int GetBoundCurveIntValue (const BoundCurve& bind);
+
+ void BoundCurveValueAwakeGeneric (Object& targetObject);
+ bool ShouldAwakeGeneric (const BoundCurve& bind);
+
+#if UNITY_EDITOR
+ // Editor API.
+ // Returns the ClassID of the bound value. (ClassID(Undefined) if it could not be bound)
+ ClassIDType GetFloatValue (Unity::GameObject& root, const EditorCurveBinding& binding, float* value);
+ ClassIDType GetPPtrValue (Unity::GameObject& root, const EditorCurveBinding& binding, int* instanceID);
+
+ bool BindEditorCurve (Unity::GameObject& root, const EditorCurveBinding& binding, BoundCurve& boundCurve);
+
+ Object* FindAnimatedObject (Unity::GameObject& root, const EditorCurveBinding& inputBinding);
+
+ ClassIDType GetEditorCurveValueClassID (Unity::GameObject& root, const EditorCurveBinding& binding);
+#endif
+}
+}
diff --git a/Runtime/Animation/KeyframeReducer.cpp b/Runtime/Animation/KeyframeReducer.cpp
new file mode 100644
index 0000000..ac3650e
--- /dev/null
+++ b/Runtime/Animation/KeyframeReducer.cpp
@@ -0,0 +1,359 @@
+#include "UnityPrefix.h"
+#include "KeyframeReducer.h"
+#include "AnimationClip.h"
+#include "Runtime/Math/Quaternion.h"
+#include "AnimationCurveUtility.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Animation/MecanimClipBuilder.h"
+#include <sstream>
+#include <vector>
+
+using namespace std;
+
+#define DEBUG_COMPRESSION 0
+
+const float kPositionMinValue = 0.00001F;
+const float kQuaternionNormalizationError = 0.001F;/// The sampled quaternion must be almost normalized.
+
+/// - We allow reduction if the reduced magnitude doesn't go off very far
+/// - And the angle between the two rotations is similar
+inline bool QuaternionDistanceError (Quaternionf value, Quaternionf reduced, const float quaternionDotError)
+{
+ float magnitude = Magnitude(reduced);
+ if (!CompareApproximately(1.0F, magnitude, kQuaternionNormalizationError))
+ {
+ return false;
+ }
+
+ value = NormalizeSafe(value);
+ reduced = reduced / magnitude;
+
+ // float angle = Rad2Deg(acos (Dot(value, reduced))) * 2.0F;
+ // if (dot > kQuaternionAngleError)
+ // return false;
+ if (Dot(value, reduced) < quaternionDotError)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool DeltaError(const float value, const float reducedValue, const float delta, const float percentage, const float minValue)
+{
+ const float absValue = Abs(value);
+ // (absValue > minValue || Abs(reducedValue) > minValue) part is necessary for reducing values which have tiny fluctuations around 0
+ return (absValue > minValue || Abs(reducedValue) > minValue) && (delta > absValue * percentage);
+}
+
+/// We allow reduction
+// - the distance of the two vectors is low
+// - the distance of each axis is low
+inline bool PositionDistanceError (Vector3f value, Vector3f reduced, const float distancePercentageError)
+{
+ const float percentage = distancePercentageError;
+ const float minValue = kPositionMinValue * percentage;
+
+ // Vector3 distance as a percentage
+ float distance = SqrMagnitude(value - reduced);
+ float length = SqrMagnitude(value);
+ float lengthReduced = SqrMagnitude(reduced);
+ //if (distance > length * Sqr(percentage))
+ if (DeltaError(length, lengthReduced, distance, Sqr(percentage), Sqr(minValue)))
+ return false;
+
+ // Distance of each axis
+ float distanceX = Abs(value.x - reduced.x);
+ float distanceY = Abs(value.y - reduced.y);
+ float distanceZ = Abs(value.z - reduced.z);
+
+ //if (distanceX > Abs(value.x) * percentage)
+ if (DeltaError(value.x, reduced.x, distanceX, percentage, minValue))
+ return false;
+ //if (distanceY > Abs(value.y) * percentage)
+ if (DeltaError(value.y, reduced.y, distanceY, percentage, minValue))
+ return false;
+ //if (distanceZ > Abs(value.z) * percentage)
+ if (DeltaError(value.z, reduced.z, distanceZ, percentage, minValue))
+ return false;
+
+ return true;
+}
+
+/// We allow reduction if the distance between the two values is low
+inline bool FloatDistanceError (float value, float reduced, const float distancePercentageError)
+{
+ const float percentage = distancePercentageError;
+ const float minValue = kPositionMinValue * percentage;
+
+ float distance = Abs(value - reduced);
+ //if (distance > Abs(value) * percentage)
+ if (DeltaError(value, reduced, distance, percentage, minValue))
+ return false;
+
+ return true;
+}
+
+// Checks if reduced curve is valid at time "time"
+template<class T, class ErrorFunction>
+bool CanReduce(AnimationCurveTpl<T>& curve, const KeyframeTpl<T>& key0, const KeyframeTpl<T>& key1, float time, ErrorFunction canReduceFunction, const float allowedError)
+{
+ T value = curve.Evaluate(time);
+ T reduced_value = InterpolateKeyframe(key0, key1, time);
+
+ return canReduceFunction(value, reduced_value, allowedError);
+}
+
+/*template<class T>
+void FitTangentsToCurve(AnimationCurveTpl<T>& curve, KeyframeTpl<T>& key0, KeyframeTpl<T>& key1)
+{
+ // perform curve fitting
+
+ const float t0 = key0.time;
+ const float dt = key1.time - key0.time;
+
+ float time1 = 0.3f;
+ float time2 = 1 - time1;
+
+ // points on the curve at time1 and time2
+ const T v0 = curve.Evaluate(t0 + time1 * dt);
+ const T v1 = curve.Evaluate(t0 + time2 * dt);
+
+ FitTangents(key0, key1, time1, time2, v0, v1);
+}*/
+
+template<class T, class ErrorFunction>
+bool CanReduce(const KeyframeTpl<T>& fromKey, const KeyframeTpl<T>& toKey, AnimationCurveTpl<T>& curve, ErrorFunction canReduceFunction, const float allowedError, const float delta,
+ int firstKey, int lastKey, bool useKeyframeLimit)
+{
+ const float beginTime = fromKey.time;
+ const float endTime = toKey.time;
+
+ // We simply sample every frame and compare the original curve against, if we simply removed one key.
+ // If the error between the curves is not too big, we just remove the key
+ bool canReduce = true;
+
+ for (float t = beginTime + delta; t < endTime; t += delta)
+ {
+ if (!(canReduce = CanReduce(curve, fromKey, toKey, t, canReduceFunction, allowedError)))
+ break;
+ }
+
+ // we need to check that all keys can be reduced, because keys might be closer to each other than delta
+ // this happens when we have steps in curve
+ // TODO : we could skip the loop above if keyframes are close enough
+
+ float lastTime = beginTime;
+
+ for (int j = firstKey; canReduce && j < lastKey; ++j)
+ {
+ const float time = curve.GetKey(j).time;
+
+ // validates point at keyframe (j) and point between (j) and and (j-1) keyframes
+ // TODO : For checking point at "time" it could just use keys[j].value instead - that would be faster than sampling the curve
+ canReduce =
+ CanReduce(curve, fromKey, toKey, time, canReduceFunction, allowedError) &&
+ CanReduce(curve, fromKey, toKey, (lastTime + time) / 2, canReduceFunction, allowedError);
+
+ lastTime = time;
+ }
+
+ if (canReduce)
+ {
+ // validate point between last two keyframes
+ float time = curve.GetKey(lastKey).time;
+ canReduce = CanReduce(curve, fromKey, toKey, (lastTime + time) / 2, canReduceFunction, allowedError);
+ }
+
+ // Don't reduce if we are about to reduce more than 50 samples at
+ // once to prevent n^2 performance impact
+ canReduce = canReduce && (!useKeyframeLimit || (endTime - beginTime < 50.0F * delta));
+
+ return canReduce;
+}
+
+template<class T, class ErrorFunction>
+float ReduceKeyframes (AnimationCurveTpl<T>& curve, float sampleRate, ErrorFunction canReduceFunction, const float allowedError, T zeroValue, bool optimalCurveRepresentation)
+{
+ AssertMsg(curve.GetKeyCount() >= 2, "Key count: %d", curve.GetKeyCount());
+
+ if (curve.GetKeyCount() <= 2)
+ return 100.0F;
+
+ dynamic_array<typename AnimationCurveTpl<T>::Keyframe> output;
+ output.reserve(curve.GetKeyCount());
+
+ float delta = 1.f / sampleRate;
+
+ // at first try to reduce to const curve
+ typename AnimationCurveTpl<T>::Keyframe firstKey = curve.GetKey(0);
+ typename AnimationCurveTpl<T>::Keyframe lastKey = curve.GetKey(curve.GetKeyCount() - 1);
+ firstKey.inSlope = firstKey.outSlope = zeroValue;
+ lastKey.inSlope = lastKey.outSlope = zeroValue;
+ lastKey.value = firstKey.value;
+
+ const bool canReduceToConstCurve = CanReduce(firstKey, lastKey, curve, canReduceFunction, allowedError, delta, 0, curve.GetKeyCount() - 1, false);
+ if (canReduceToConstCurve)
+ {
+ output.reserve(2);
+ output.push_back(firstKey);
+ output.push_back(lastKey);
+ }
+ else
+ {
+ output.reserve(curve.GetKeyCount());
+ // We always add the first key
+ output.push_back(curve.GetKey(0));
+
+ int lastUsedKey = 0;
+
+ for (int i=1;i<curve.GetKeyCount() - 1;i++)
+ {
+ typename AnimationCurveTpl<T>::Keyframe fromKey = curve.GetKey(lastUsedKey);
+ typename AnimationCurveTpl<T>::Keyframe toKey = curve.GetKey(i + 1);
+
+ //FitTangentsToCurve(curve, fromKey, toKey);
+
+ const bool canReduce = CanReduce(fromKey, toKey, curve, canReduceFunction, allowedError, delta, lastUsedKey + 1, i + 1, true);
+
+ if (!canReduce)
+ {
+ output.push_back(curve.GetKey(i));
+ // fitting tangents between last two keys
+ //FitTangentsToCurve(curve, *(output.end() - 2), output.back());
+
+ lastUsedKey = i;
+ }
+ }
+
+ // We always add the last key
+ output.push_back(curve.GetKey(curve.GetKeyCount() - 1));
+ // fitting tangents between last and the one before last keys
+ //FitTangentsToCurve(curve, *(output.end() - 2), output.back());
+ }
+
+ float reduction = (float)output.size() / (float)curve.GetKeyCount() * 100.0F;
+
+ curve.Swap(output);
+
+ // if we want optimal curve representation and reduced curve can be represeted with a dense curve
+ // keep original curve for better quality sampling
+ if (optimalCurveRepresentation && IsDenseCurve(curve))
+ {
+ curve.Swap(output);
+ reduction = 100.0F;
+ }
+
+ return reduction;
+}
+
+template <class T, class U, typename ReductionFunction>
+float ReduceKeyframes (const float sampleRate, T& curves, ReductionFunction reductionFunction, const float allowedError, const U zeroValue, bool optimalCurveRepresentation)
+{
+ float totalRatio = 0;
+ for (typename T::iterator it = curves.begin(), end = curves.end(); it != end; ++it)
+ {
+ float compressionRatio = ReduceKeyframes(it->curve, sampleRate, reductionFunction, allowedError, zeroValue, optimalCurveRepresentation);
+ #if DEBUG_COMPRESSION
+ printf_console ("Compression %f%% of rotation %s\n", compressionRatio, i->path.c_str());
+ #endif
+
+ totalRatio += compressionRatio;
+ }
+
+ return totalRatio;
+}
+
+void ReduceKeyframes (AnimationClip& clip, float rotationError, float positionError, float scaleError, float floatError)
+{
+ AnimationClip::QuaternionCurves& rot = clip.GetRotationCurves();
+ AnimationClip::Vector3Curves& pos = clip.GetPositionCurves();
+ AnimationClip::Vector3Curves& scale = clip.GetScaleCurves();
+ AnimationClip::FloatCurves& floats = clip.GetFloatCurves();
+ AnimationClip::FloatCurves& editorCurves = clip.GetEditorCurvesNoConversion();
+
+ rotationError = cos(Deg2Rad(rotationError) / 2.0F);
+ positionError = positionError / 100.0F;
+ scaleError = scaleError / 100.0F;
+ floatError = floatError / 100.0F;
+
+ float time = GetTimeSinceStartup();
+ float averageCompressionRatio = 0;
+
+ const float sampleRate = clip.GetSampleRate();
+
+ averageCompressionRatio += ReduceKeyframes (sampleRate, rot, QuaternionDistanceError, rotationError, Quaternionf(0, 0, 0, 0), clip.IsAnimatorMotion() && !clip.GetUseHighQualityCurve() );
+ averageCompressionRatio += ReduceKeyframes (sampleRate, pos, PositionDistanceError, positionError, Vector3f::zero, clip.IsAnimatorMotion() && !clip.GetUseHighQualityCurve());
+ averageCompressionRatio += ReduceKeyframes (sampleRate, scale, PositionDistanceError, scaleError, Vector3f::zero, clip.IsAnimatorMotion() && !clip.GetUseHighQualityCurve());
+ averageCompressionRatio += ReduceKeyframes (sampleRate, floats, FloatDistanceError, floatError, 0.f, clip.IsAnimatorMotion() && !clip.GetUseHighQualityCurve());
+ averageCompressionRatio += ReduceKeyframes (sampleRate, editorCurves, FloatDistanceError, floatError, 0.f, clip.IsAnimatorMotion() && !clip.GetUseHighQualityCurve());
+
+ // If all the curves are empty, we end up with zero reduction.
+ if (averageCompressionRatio == 0)
+ return;
+
+ averageCompressionRatio /= (rot.size() + pos.size() + scale.size() + floats.size() + editorCurves.size());
+ time = GetTimeSinceStartup() - time;
+
+ {
+ std::ostringstream oss;
+ oss << "Keyframe reduction: Ratio: " << averageCompressionRatio << "%; Time: " << time << "s;\n";
+ printf_console("%s", oss.str().c_str());
+ }
+}
+
+// TODO : use tangents from editor or perform curve fitting
+// TODO : these should be removed eventually and custom settings should be used instead
+const float kQuaternionAngleError = 0.5F;// The maximum angle deviation allowed in degrees
+const float kQuaternionDotError = cos(Deg2Rad(kQuaternionAngleError) / 2.0F);
+///@TODO: * Support step curves
+/// * Improve keyframe reduction
+/// * Make keyframe reduction faster
+void EulerToQuaternionCurveBake (const AnimationCurve& curveX, const AnimationCurve& curveY, const AnimationCurve& curveZ, AnimationCurveQuat& collapsed, float sampleRate)
+{
+ float begin = std::numeric_limits<float>::infinity ();
+ float end = -std::numeric_limits<float>::infinity ();
+
+ float delta = 1.0F / sampleRate;
+
+ const AnimationCurve* curves[3] = { &curveX, &curveY, &curveZ };
+
+ for (int i=0;i<3;i++)
+ {
+ if (curves[i]->GetKeyCount() >= 1)
+ {
+ begin = min(curves[i]->GetKey(0).time, begin);
+ end = max(curves[i]->GetKey(curves[i]->GetKeyCount()-1).time, end);
+ }
+ }
+ if (!IsFinite(begin) || !IsFinite(end))
+ return;
+
+
+ float deg2rad = Deg2Rad(1.0F);
+ for (float i=begin;i < end + delta;i += delta)
+ {
+ if (i + delta / 2.0F > end)
+ i = end;
+
+ Vector3f euler = Vector3f (curveX.Evaluate(i), curveY.Evaluate(i), curveZ.Evaluate(i)) * deg2rad;
+ Quaternionf q = EulerToQuaternion(euler);
+ //Vector3f eulerBack = QuaternionToEuler (q) * Rad2Deg(1.0F);
+ //printf_console("%f : %f, %f, %f ----- %f, %f, %f\n", i, euler.x, euler.y, euler.z, eulerBack.x, eulerBack.y, eulerBack.z);
+
+ KeyframeTpl<Quaternionf> key;
+ key.time = i;
+ key.value = q;
+ key.inSlope = key.outSlope = Quaternionf(0,0,0,0);
+ collapsed.AddKeyBackFast(key);
+
+ if (i == end)
+ break;
+ }
+
+ EnsureQuaternionContinuityAndRecalculateSlope (collapsed);
+
+ //@TODO: Keyframe reduction is disabled for now, with keyframe reduction enabled iteration time
+ // in the animation window becomes unbearable
+ // ReduceKeyframes(collapsed, sampleRate, QuaternionDistanceError, kQuaternionDotError, Quaternionf(0, 0, 0, 0), false);
+}
diff --git a/Runtime/Animation/KeyframeReducer.h b/Runtime/Animation/KeyframeReducer.h
new file mode 100644
index 0000000..5fb6949
--- /dev/null
+++ b/Runtime/Animation/KeyframeReducer.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class AnimationClip;
+#include "Runtime/Math/AnimationCurve.h"
+
+// Rotation error is defined as maximum angle deviation allowed in degrees
+// For others it is defined as maximum distance/delta deviation allowed in percents
+void ReduceKeyframes (AnimationClip& clip, float rotationError, float positionError, float scaleError, float floatError);
+
+void EulerToQuaternionCurveBake (const AnimationCurve& curveX, const AnimationCurve& curveY, const AnimationCurve& curveZ, AnimationCurveQuat& collapsed, float sampleRate);
diff --git a/Runtime/Animation/MecanimAnimation.cpp b/Runtime/Animation/MecanimAnimation.cpp
new file mode 100644
index 0000000..8d8ca20
--- /dev/null
+++ b/Runtime/Animation/MecanimAnimation.cpp
@@ -0,0 +1,78 @@
+#include "UnityPrefix.h"
+
+#include "MecanimAnimation.h"
+
+#include "Animator.h"
+#include "Avatar.h"
+#include "CalculateAnimatorSkinMatrices.h"
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/Utilities/Word.h"
+
+void MecanimAnimation::InitializeClass ()
+{
+ SetAnimationInterface(new MecanimAnimation());
+}
+
+void MecanimAnimation::CleanupClass ()
+{
+ MecanimAnimation* animation = reinterpret_cast<MecanimAnimation*>(GetAnimationInterface ());
+ delete animation;
+ SetAnimationInterface(NULL);
+}
+
+const void* MecanimAnimation::GetGlobalSpaceSkeletonPose(const Unity::Component& animatorComponent)
+{
+ const Animator& animator = static_cast<const Animator&>(animatorComponent);
+ return animator.GetGlobalSpaceSkeletonPose();
+}
+
+CalculateAnimatorSkinMatricesFunc MecanimAnimation::GetCalculateAnimatorSkinMatricesFunc()
+{
+ return DoCalculateAnimatorSkinMatrices;
+}
+
+bool MecanimAnimation::CalculateWorldSpaceMatricesMainThread(Unity::Component& animatorComponent, const UInt16* indices, size_t count, Matrix4x4f* outMatrices)
+{
+ Animator& animator = static_cast<Animator&>(animatorComponent);
+ AssertIf(animator.GetHasTransformHierarchy());
+
+ return CalculateWordSpaceMatrices(&animator, indices, outMatrices, count);
+}
+
+bool MecanimAnimation::PathHashesToIndices(Unity::Component& animatorComponent, const BindingHash* bonePathHashes, size_t count, UInt16* outIndices)
+{
+ Animator& animator = static_cast<Animator&>(animatorComponent);
+ if (animator.GetHasTransformHierarchy())
+ return false;
+
+ const mecanim::animation::AvatarConstant* avatarConstant = animator.GetAvatarConstant();
+ if (!avatarConstant)
+ return false;
+
+ const mecanim::skeleton::Skeleton* skel = avatarConstant->m_AvatarSkeleton.Get();
+ if (!skel)
+ return false;
+
+ bool doMatchSkeleton = true;
+ for (int i = 0; i < count && doMatchSkeleton; i++)
+ {
+ int skeletonIndex = mecanim::skeleton::SkeletonFindNode(skel, bonePathHashes[i]);
+ doMatchSkeleton = (skeletonIndex != -1);
+ outIndices[i] = (UInt16)skeletonIndex;
+ }
+
+ if (!doMatchSkeleton)
+ {
+ const Avatar* avatar = animator.GetAvatar();
+ Assert(avatar);
+ ErrorStringObject(Format("The input bones do not match the skeleton of the Avatar(%s).\n"
+ "Please check if the Avatar is generated in optimized mode, or if the Avatar is valid for the attached SkinnedMeshRenderer.",
+ avatar->GetName()).c_str(),
+ avatar);
+ }
+
+ return doMatchSkeleton;
+}
diff --git a/Runtime/Animation/MecanimAnimation.h b/Runtime/Animation/MecanimAnimation.h
new file mode 100644
index 0000000..aea8190
--- /dev/null
+++ b/Runtime/Animation/MecanimAnimation.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "Runtime/Interfaces/IAnimation.h"
+
+
+class MecanimAnimation : public IAnimation
+{
+public:
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ MecanimAnimation() {}
+ ~MecanimAnimation() {}
+
+ virtual const void* GetGlobalSpaceSkeletonPose(const Unity::Component& animator);
+
+ virtual bool CalculateWorldSpaceMatricesMainThread(Unity::Component& animator, const UInt16* indices, size_t count, Matrix4x4f* outMatrices);
+
+ virtual CalculateAnimatorSkinMatricesFunc GetCalculateAnimatorSkinMatricesFunc();
+
+ virtual bool PathHashesToIndices(Unity::Component& animator, const BindingHash* bonePathHashes, size_t count, UInt16* outIndices);
+
+}; \ No newline at end of file
diff --git a/Runtime/Animation/MecanimArraySerialization.h b/Runtime/Animation/MecanimArraySerialization.h
new file mode 100644
index 0000000..204e134
--- /dev/null
+++ b/Runtime/Animation/MecanimArraySerialization.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#include "Runtime/mecanim/memory.h"
+
+template<typename T, int SIZE>
+struct StaticArrayTransfer
+{
+ enum
+ {
+ m_ArraySize = SIZE
+ };
+
+ size_t m_Size;
+
+ typedef T* iterator;
+ typedef T value_type;
+
+ T (&m_Data)[SIZE];
+
+ StaticArrayTransfer (T (&data)[SIZE]):m_Data(data),m_Size(m_ArraySize)
+ {
+ }
+
+ void reserve(size_t size)
+ {
+ m_Size = std::min<size_t>(size, m_ArraySize);
+ }
+
+ iterator begin () { return &m_Data[0]; }
+ iterator end () { return &m_Data[m_Size]; }
+ size_t size() { return m_Size; }
+};
+
+template<typename T, int SIZE>
+class SerializeTraits< StaticArrayTransfer<T, SIZE> > : public SerializeTraitsBase< StaticArrayTransfer<T, SIZE> >
+{
+public:
+
+ DEFINE_GET_TYPESTRING_CONTAINER (staticvector)
+
+ typedef StaticArrayTransfer<T, SIZE> value_type;
+
+ static size_t GetAlignOf() {return ALIGN_OF(T);}
+
+ template<class TransferFunction> inline
+ static void Transfer (value_type& data, TransferFunction& transfer)
+ {
+ transfer.TransferSTLStyleArray (data);
+ }
+
+ static bool IsContinousMemoryArray () { return true; }
+ static void ResizeSTLStyleArray (value_type& data, int rs)
+ {
+ data.reserve(rs);
+ }
+
+ static void resource_image_assign_external (value_type& data, void* begin, void* end)
+ {
+ }
+};
+
+#define STATIC_ARRAY_TRANSFER(TYPE,DATA,SIZE) StaticArrayTransfer<TYPE, SIZE> DATA##ArrayTransfer (DATA); transfer.Transfer(DATA##ArrayTransfer, #DATA);
+
+
+template<class T, class TransferFunction>
+struct ManualArrayTransfer
+{
+ typedef T* iterator;
+ typedef T value_type;
+
+ T** m_Data;
+ mecanim::uint32_t* m_ArraySize;
+ void* m_Allocator;
+ TransferFunction& m_Transfer;
+
+ ManualArrayTransfer (T*& data, mecanim::uint32_t& size, void* allocator, TransferFunction& transfer):m_Transfer(transfer)
+ {
+ m_Allocator = allocator;
+ m_Data = &data;
+ m_ArraySize = &size;
+ }
+
+ T* begin () { return *m_Data; }
+ T* end () { return *m_Data + *m_ArraySize; }
+ size_t size() { return *m_ArraySize; }
+
+ void resize (int size)
+ {
+ if(m_Transfer.IsReading() || m_Transfer.IsWriting() || m_Transfer.IsRemapPPtrTransfer())
+ {
+ mecanim::memory::ChainedAllocator* allocator = static_cast<mecanim::memory::ChainedAllocator*> (m_Allocator);
+ Assert(allocator != NULL);
+
+ *m_Data = allocator->ConstructArray<T> (size);
+ *m_ArraySize = size;
+ }
+ }
+};
+
+template<class T, class TransferFunction>
+struct ManualArrayTransfer<T*, TransferFunction>
+{
+ typedef T** iterator;
+ typedef T* value_type;
+
+ value_type** m_Data;
+ mecanim::uint32_t* m_ArraySize;
+ void* m_Allocator;
+ TransferFunction& m_Transfer;
+
+ ManualArrayTransfer (value_type *& data, mecanim::uint32_t& size, void* allocator, TransferFunction& transfer):m_Transfer(transfer)
+ {
+ m_Allocator = allocator;
+ m_Data = &data;
+ m_ArraySize = &size;
+ }
+
+ value_type* begin () { return *m_Data; }
+ value_type* end () { return *m_Data + *m_ArraySize; }
+ size_t size() { return *m_ArraySize; }
+
+ void resize (int size)
+ {
+ if(m_Transfer.IsReading() || m_Transfer.IsWriting() || m_Transfer.IsRemapPPtrTransfer())
+ {
+ mecanim::memory::ChainedAllocator* allocator = static_cast<mecanim::memory::ChainedAllocator*> (m_Allocator);
+ Assert(allocator != NULL);
+
+ *m_Data = allocator->ConstructArray<value_type> (size);
+ memset(*m_Data, 0, sizeof(value_type)*size);
+ *m_ArraySize = size;
+ }
+ }
+};
+
+
+
+template<class T, class TransferFunction2>
+class SerializeTraits<ManualArrayTransfer<T, TransferFunction2> > : public SerializeTraitsBase<ManualArrayTransfer<T, TransferFunction2> >
+{
+public:
+
+ typedef ManualArrayTransfer<T, TransferFunction2> value_type;
+ DEFINE_GET_TYPESTRING_CONTAINER (vector)
+
+ template<class TransferFunction> inline
+ static void Transfer (value_type& data, TransferFunction& transfer)
+ {
+ transfer.TransferSTLStyleArray (data);
+ }
+
+ static bool IsContinousMemoryArray () { return true; }
+ static void ResizeSTLStyleArray (value_type& data, int rs)
+ {
+ data.resize(rs);
+ }
+
+ static void resource_image_assign_external (value_type& data, void* begin, void* end)
+ {
+ }
+};
+
+#define TRANSFER_NULLABLE(x,TYPE) \
+if (transfer.IsReading () || transfer.IsWriting ()) \
+{ \
+ if (x == NULL) \
+ { \
+ mecanim::memory::ChainedAllocator* allocator = static_cast<mecanim::memory::ChainedAllocator*> (transfer.GetUserData()); \
+ x = allocator->Construct<TYPE>(); \
+ } \
+ transfer.Transfer(*x, #x); \
+} \
+else \
+{ \
+ TYPE p; \
+ transfer.Transfer(p, #x); \
+}
+
diff --git a/Runtime/Animation/MecanimClipBuilder.cpp b/Runtime/Animation/MecanimClipBuilder.cpp
new file mode 100644
index 0000000..72565ee
--- /dev/null
+++ b/Runtime/Animation/MecanimClipBuilder.cpp
@@ -0,0 +1,349 @@
+#include "UnityPrefix.h"
+#include "MecanimClipBuilder.h"
+#include "Runtime/mecanim/animation/clipmuscle.h"
+#include "StreamedClipBuilder.h"
+#include "DenseClipBuilder.h"
+#include "GenericAnimationBindingCache.h"
+#include "AnimationClipSettings.h"
+
+MecanimClipBuilder::MecanimClipBuilder ()
+ :hasAnimationEvents(false),
+ startTime(std::numeric_limits<float>::infinity()),
+ stopTime(-std::numeric_limits<float>::infinity()),
+ sampleRate (30.0F)
+{
+ // Muscle curves
+ for(mecanim::uint32_t muscleIter = 0; muscleIter < mecanim::animation::s_ClipMuscleCurveCount; muscleIter++)
+ muscleIndexArray[muscleIter] = -1;
+}
+
+void PatchMuscleClipWithInfo (const AnimationClipSettings& clipInfo, bool isHumanoid, mecanim::animation::ClipMuscleConstant *cst)
+{
+ cst->m_StartTime = clipInfo.m_StartTime;
+ cst->m_StopTime = clipInfo.m_StopTime;
+ cst->m_OrientationOffsetY = clipInfo.m_OrientationOffsetY;
+ cst->m_Level = clipInfo.m_Level;
+ cst->m_CycleOffset = clipInfo.m_CycleOffset;
+ cst->m_LoopTime = clipInfo.m_LoopTime;
+ cst->m_LoopBlend = clipInfo.m_LoopBlend;
+ cst->m_LoopBlendOrientation = clipInfo.m_LoopBlendOrientation;
+ cst->m_LoopBlendPositionY = clipInfo.m_LoopBlendPositionY;
+ cst->m_LoopBlendPositionXZ = clipInfo.m_LoopBlendPositionXZ;
+ cst->m_KeepOriginalOrientation = clipInfo.m_KeepOriginalOrientation;
+ cst->m_KeepOriginalPositionY = clipInfo.m_KeepOriginalPositionY;
+ cst->m_KeepOriginalPositionXZ = clipInfo.m_KeepOriginalPositionXZ;
+ cst->m_HeightFromFeet = clipInfo.m_HeightFromFeet;
+ cst->m_Mirror = clipInfo.m_Mirror;
+
+ if (isHumanoid)
+ {
+ mecanim::animation::InitClipMuscleDeltaPose (*cst);
+ mecanim::animation::InitClipMuscleAverageSpeed (*cst);
+ }
+ mecanim::animation::InitClipMuscleDeltaValues (*cst);
+}
+
+void CstToAnimationClipSettings (mecanim::animation::ClipMuscleConstant const *cst, AnimationClipSettings &clipInfo)
+{
+ clipInfo.m_StartTime = cst->m_StartTime;
+ clipInfo.m_StopTime = cst->m_StopTime;
+ clipInfo.m_OrientationOffsetY = cst->m_OrientationOffsetY;
+ clipInfo.m_Level = cst->m_Level;
+ clipInfo.m_CycleOffset = cst->m_CycleOffset;
+ clipInfo.m_LoopTime = cst->m_LoopTime;
+ clipInfo.m_LoopBlend = cst->m_LoopBlend;
+ clipInfo.m_LoopBlendOrientation = cst->m_LoopBlendOrientation;
+ clipInfo.m_LoopBlendPositionY = cst->m_LoopBlendPositionY;
+ clipInfo.m_LoopBlendPositionXZ = cst->m_LoopBlendPositionXZ;
+ clipInfo.m_KeepOriginalOrientation = cst->m_KeepOriginalOrientation;
+ clipInfo.m_KeepOriginalPositionY = cst->m_KeepOriginalPositionY;
+ clipInfo.m_KeepOriginalPositionXZ = cst->m_KeepOriginalPositionXZ;
+ clipInfo.m_HeightFromFeet = cst->m_HeightFromFeet;
+ clipInfo.m_Mirror = cst->m_Mirror;
+}
+
+///@TODO: On the runs.fbx there are a ton of curves. Check up on what is going on...
+
+template<class T>
+static ClipOptType ClassifyCurve (AnimationCurveTpl<T>& curve, bool useHighQualityCurve)
+{
+ if (curve.GetKeyCount() == 0)
+ return kInvalidCurve;
+
+ if (IsConstantCurve(curve))
+ return kConstantClip;
+
+ if (!useHighQualityCurve && IsDenseCurve(curve))
+ return kDenseClip;
+
+ return kStreamedClip;
+}
+
+template<class T>
+static void AddCurveToConstantClip (mecanim::animation::ConstantClip& clip, int index, AnimationCurveTpl<T>& curve)
+{
+ memcpy(&clip.data[index], &curve.GetKey(0).value, sizeof(T));
+}
+
+static void AddMappedPPtrCurveToStreamedClip (StreamedClipBuilder* builder, int curveIter, UnityEngine::Animation::AnimationClipBindingConstant& clipBindings, const PPtrKeyframes& pptrCurve)
+{
+ const size_t keyframeCount = pptrCurve.size();
+
+ float* inTime;
+ int* inValue;
+ ALLOC_TEMP(inTime, float, keyframeCount);
+ ALLOC_TEMP(inValue, int, keyframeCount);
+
+ const int mapOffset = clipBindings.pptrCurveMapping.size();
+ for (int i = 0; i < keyframeCount; ++i)
+ {
+ inTime[i] = pptrCurve[i].time;
+ // Map Object to index
+ inValue[i] = mapOffset + i;
+ clipBindings.pptrCurveMapping.push_back(pptrCurve[i].value);
+ }
+
+ AddIntegerCurveToStreamedClip(builder, curveIter, inTime, inValue, keyframeCount);
+}
+
+template<typename TYPE> void for_each_curve(MecanimClipBuilder& clipBuilder, TYPE const& curves )
+{
+ for (int i=0;i<curves.size();i++)
+ {
+ std::pair<float, float> range = curves[i]->GetRange ();
+ clipBuilder.startTime = std::min(range.first, clipBuilder.startTime);
+ clipBuilder.stopTime = std::max(range.second, clipBuilder.stopTime);
+ }
+}
+
+void ComputeDenseClipRange(MecanimClipBuilder& clipBuilder)
+{
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[kDenseClip];
+ for_each_curve(clipBuilder, curves.positionCurves);
+ for_each_curve(clipBuilder, curves.rotationCurves);
+ for_each_curve(clipBuilder, curves.scaleCurves);
+ for_each_curve(clipBuilder, curves.genericCurves);
+
+ for (int i=0;i<curves.pptrCurves.size();i++)
+ {
+ PPtrKeyframes& keyFrames = *curves.pptrCurves[i];
+ for(int j=0;j<keyFrames.size();j++)
+ {
+ clipBuilder.startTime = std::min(keyFrames[j].time, clipBuilder.startTime);
+ clipBuilder.stopTime = std::max(keyFrames[j].time, clipBuilder.stopTime);
+ }
+ }
+
+ clipBuilder.startTime = !IsFinite(clipBuilder.startTime) ? 0.0f : clipBuilder.startTime;
+ clipBuilder.stopTime = !IsFinite(clipBuilder.stopTime) ? 0.0f : clipBuilder.stopTime;
+}
+
+bool PrepareClipBuilder (MecanimClipBuilder& clipBuilder)
+{
+ size_t previousTypesCurveCount = 0;
+
+ ComputeDenseClipRange(clipBuilder);
+
+ for (int t=0;t<kClipOptCount;t++)
+ {
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[t];
+
+ size_t keyCount = 0;
+ size_t genericBindingIndex = 0;
+ size_t curveCount = 0;
+ for (int i=0;i<curves.positionCurves.size();i++)
+ {
+ keyCount += curves.positionCurves[i]->GetKeyCount() * 3;
+ curveCount += 3;
+ genericBindingIndex++;
+ }
+
+ for (int i=0;i<curves.rotationCurves.size();i++)
+ {
+ keyCount += curves.rotationCurves[i]->GetKeyCount() * 4;
+ curveCount += 4;
+ genericBindingIndex++;
+ }
+
+ for (int i=0;i<curves.scaleCurves.size();i++)
+ {
+ keyCount += curves.scaleCurves[i]->GetKeyCount() * 3;
+ curveCount += 3;
+ genericBindingIndex++;
+ }
+
+ for (int i=0;i<curves.genericCurves.size();i++)
+ {
+ if (IsMuscleBinding(curves.bindings[genericBindingIndex]))
+ clipBuilder.muscleIndexArray[curves.bindings[genericBindingIndex].attribute] = curveCount + previousTypesCurveCount;
+
+ keyCount += curves.genericCurves[i]->GetKeyCount();
+ genericBindingIndex++;
+ curveCount++;
+ }
+
+ for (int i=0;i<curves.pptrCurves.size();i++)
+ {
+ keyCount += curves.pptrCurves[i]->size();
+ curveCount++;
+ }
+
+ curves.totalKeyCount = keyCount;
+ curves.totalCurveCount = curveCount;
+ previousTypesCurveCount += curveCount;
+ }
+
+ clipBuilder.totalCurveCount = 0;
+ clipBuilder.totalBindingCount = 0;
+ for (int t=0;t<kClipOptCount;t++)
+ {
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[t];
+ clipBuilder.totalBindingCount += curves.bindings.size();
+ clipBuilder.totalCurveCount += curves.totalCurveCount;
+ }
+
+ return clipBuilder.totalCurveCount != 0 || clipBuilder.hasAnimationEvents;
+}
+
+mecanim::animation::ClipMuscleConstant* BuildMuscleClip (const MecanimClipBuilder& clipBuilder, const AnimationClipSettings& animationClipSettings, bool isHumanClip, UnityEngine::Animation::AnimationClipBindingConstant& outClipBindings, mecanim::memory::Allocator& allocator)
+{
+ SETPROFILERLABEL(ClipMuscleConstant);
+
+ // Total binding count
+ outClipBindings.genericBindings.clear();
+ outClipBindings.genericBindings.reserve(clipBuilder.totalBindingCount);
+ outClipBindings.pptrCurveMapping.clear();
+
+ // Combine into a single binding array
+ outClipBindings.genericBindings.reserve(clipBuilder.totalBindingCount);
+ for (int i=0;i<kClipOptCount;i++)
+ outClipBindings.genericBindings.insert(outClipBindings.genericBindings.end(), clipBuilder.curves[i].bindings.begin(), clipBuilder.curves[i].bindings.end());
+
+ mecanim::animation::Clip* clip = mecanim::animation::CreateClipSimple (clipBuilder.totalCurveCount, allocator);
+
+ // Streamed clip
+ const MecanimClipBuilder::Curves& streamedCurves = clipBuilder.curves[kStreamedClip];
+ StreamedClipBuilder* builder = NULL;
+ builder = CreateStreamedClipBuilder(streamedCurves.totalCurveCount, streamedCurves.totalKeyCount);
+
+ // Constant clip
+ const MecanimClipBuilder::Curves& constantCurves = clipBuilder.curves[kConstantClip];
+ CreateConstantClip (clip->m_ConstantClip, constantCurves.totalCurveCount, allocator);
+
+ // Dense clip
+ const MecanimClipBuilder::Curves& denseCurves = clipBuilder.curves[kDenseClip];
+ CreateDenseClip (clip->m_DenseClip, denseCurves.totalCurveCount, clipBuilder.startTime, clipBuilder.stopTime, clipBuilder.sampleRate, allocator);
+
+ for (int t=0;t<kClipOptCount;t++)
+ {
+ const MecanimClipBuilder::Curves& curves = clipBuilder.curves[t];
+
+ #define AddCurveByType(CURVE_TYPE) \
+ if (t == kStreamedClip) \
+ AddCurveToStreamedClip(builder, curveIter, *curves.CURVE_TYPE[i]); \
+ else if (t == kDenseClip) \
+ AddCurveToDenseClip(clip->m_DenseClip, curveIter, *curves.CURVE_TYPE[i]); \
+ else if (t == kConstantClip) \
+ AddCurveToConstantClip (clip->m_ConstantClip, curveIter, *curves.CURVE_TYPE[i]);
+
+ size_t curveIter = 0;
+ for (int i=0;i<curves.positionCurves.size();i++, curveIter+=3)
+ {
+ AddCurveByType(positionCurves)
+ }
+
+ for (int i=0;i<curves.rotationCurves.size();i++, curveIter+=4)
+ {
+ AddCurveByType(rotationCurves)
+ }
+
+ for (int i=0;i<curves.scaleCurves.size();i++,curveIter+=3)
+ {
+ AddCurveByType(scaleCurves)
+ }
+
+ for (int i=0;i<curves.genericCurves.size();i++,curveIter++)
+ {
+ AddCurveByType(genericCurves)
+ }
+
+ for (int i=0;i<curves.pptrCurves.size();i++,curveIter++)
+ {
+ Assert(t == kStreamedClip);
+ AddMappedPPtrCurveToStreamedClip(builder, curveIter, outClipBindings, *streamedCurves.pptrCurves[i]);
+ }
+ }
+
+ if (builder)
+ {
+ CreateStreamClipConstant(builder, clip->m_StreamedClip, allocator);
+ DestroyStreamedClipBuilder(builder);
+ }
+
+ mecanim::animation::ClipMuscleConstant* muscleClip = mecanim::animation::CreateClipMuscleConstant(clip, allocator);
+ for(mecanim::uint32_t muscleIter = 0; muscleIter < mecanim::animation::s_ClipMuscleCurveCount; muscleIter++)
+ muscleClip->m_IndexArray[muscleIter] = clipBuilder.muscleIndexArray[muscleIter];
+
+ PatchMuscleClipWithInfo (animationClipSettings, isHumanClip, muscleClip);
+
+ return muscleClip;
+}
+
+void AddPositionCurveToClipBuilder (AnimationCurveVec3& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve)
+{
+ ClipOptType type = ClassifyCurve (curve, useHighQualityCurve);
+ if (type == kInvalidCurve)
+ return;
+
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[type];
+ curves.positionCurves.push_back(&curve);
+ CreateTransformBinding (path, UnityEngine::Animation::kBindTransformPosition, curves.bindings.push_back());
+}
+
+void AddRotationCurveToClipBuilder (AnimationCurveQuat& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve)
+{
+ ClipOptType type = ClassifyCurve (curve, useHighQualityCurve);
+ if (type == kInvalidCurve)
+ return;
+
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[type];
+ curves.rotationCurves.push_back(&curve);
+ CreateTransformBinding (path, UnityEngine::Animation::kBindTransformRotation, curves.bindings.push_back());
+}
+
+void AddScaleCurveToClipBuilder (AnimationCurveVec3& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve)
+{
+ ClipOptType type = ClassifyCurve (curve, useHighQualityCurve);
+ if (type == kInvalidCurve)
+ return;
+
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[type];
+ curves.scaleCurves.push_back(&curve);
+ CreateTransformBinding (path, UnityEngine::Animation::kBindTransformScale, curves.bindings.push_back());
+}
+
+void AddGenericCurveToClipBuilder (AnimationCurve& curve, const UnityEngine::Animation::GenericBinding& binding, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve)
+{
+ ClipOptType type = ClassifyCurve (curve, useHighQualityCurve);
+ if (type == kInvalidCurve)
+ return;
+
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[type];
+ curves.genericCurves.push_back(&curve);
+ curves.bindings.push_back(binding);
+}
+
+void AddPPtrCurveToClipBuilder (PPtrKeyframes& curve, const UnityEngine::Animation::GenericBinding& binding, MecanimClipBuilder& clipBuilder)
+{
+ if (curve.empty())
+ return;
+
+ // The runtime binding code is not able to handle when a Transform component incorrectly has pptr curve.
+ // Reject it here..
+ if (binding.classID == ClassID(Transform))
+ return;
+
+ MecanimClipBuilder::Curves& curves = clipBuilder.curves[kStreamedClip];
+ curves.pptrCurves.push_back(&curve);
+ curves.bindings.push_back(binding);
+}
diff --git a/Runtime/Animation/MecanimClipBuilder.h b/Runtime/Animation/MecanimClipBuilder.h
new file mode 100644
index 0000000..d79dd20
--- /dev/null
+++ b/Runtime/Animation/MecanimClipBuilder.h
@@ -0,0 +1,128 @@
+#pragma once
+
+#include "PPtrKeyframes.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "AnimationClipBindings.h"
+#include "Runtime/mecanim/animation/clipmuscle.h"
+
+/// Builds a full mecanim clip from source curve data
+/// When building a mecanim clip we classify all curves into:
+/// streamedclip: hermite curve polynomials
+/// denseclip: linearly interpolated non-sparse keyframes
+/// constantclip: value doesn't change over time
+
+
+struct AnimationClipSettings;
+
+enum ClipOptType { kInvalidCurve = -1, kStreamedClip = 0, kDenseClip, kConstantClip, kClipOptCount };
+
+struct MecanimClipBuilder
+{
+ struct Curves
+ {
+ dynamic_array<AnimationCurveVec3*> positionCurves;
+ dynamic_array<AnimationCurveQuat*> rotationCurves;
+ dynamic_array<AnimationCurveVec3*> scaleCurves;
+ dynamic_array<AnimationCurve*> genericCurves;
+ dynamic_array<PPtrKeyframes*> pptrCurves;
+
+ size_t totalCurveCount;
+ size_t totalKeyCount;
+
+ dynamic_array<UnityEngine::Animation::GenericBinding> bindings;
+ };
+
+ MecanimClipBuilder ();
+
+ mecanim::uint32_t muscleIndexArray[mecanim::animation::s_ClipMuscleCurveCount];
+
+ Curves curves[kClipOptCount];
+ size_t totalBindingCount;
+ size_t totalCurveCount;
+ bool hasAnimationEvents;
+ float startTime;
+ float stopTime;
+ float sampleRate;
+};
+
+void AddPositionCurveToClipBuilder (AnimationCurveVec3& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve);
+void AddRotationCurveToClipBuilder (AnimationCurveQuat& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve);
+void AddScaleCurveToClipBuilder (AnimationCurveVec3& curve, const UnityStr& path, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve);
+void AddGenericCurveToClipBuilder (AnimationCurve& curve, const UnityEngine::Animation::GenericBinding& binding, MecanimClipBuilder& clipBuilder, bool useHighQualityCurve);
+void AddPPtrCurveToClipBuilder (PPtrKeyframes& curve, const UnityEngine::Animation::GenericBinding& binding, MecanimClipBuilder& clipBuilder);
+
+bool PrepareClipBuilder (MecanimClipBuilder& clipBuilder);
+mecanim::animation::ClipMuscleConstant* BuildMuscleClip (const MecanimClipBuilder& clipBuilder, const AnimationClipSettings& muslceClipInfo, bool isHumanClip, UnityEngine::Animation::AnimationClipBindingConstant& outClipBindings, mecanim::memory::Allocator& allocator);
+
+void PatchMuscleClipWithInfo (const AnimationClipSettings& clipInfo, bool isHumanoid, mecanim::animation::ClipMuscleConstant *cst);
+void CstToAnimationClipSettings (mecanim::animation::ClipMuscleConstant const *cst, AnimationClipSettings &clipInfo);
+
+template<class T>
+static bool IsConstantCurve (AnimationCurveTpl<T>& curve)
+{
+ Assert(curve.GetKeyCount() != 0);
+
+ KeyframeTpl<T> firstKey = curve.GetKey(0);
+ for (int i=0;i<curve.GetKeyCount();i++)
+ {
+ if (!CompareApproximately(curve.GetKey(i).value, firstKey.value))
+ return false;
+ if (!CompareApproximately(curve.GetKey(i).inSlope, Zero<T> ()))
+ return false;
+ if (!CompareApproximately(curve.GetKey(i).outSlope, Zero<T> ()))
+ return false;
+ }
+
+ return true;
+}
+
+template<class T>
+static bool IsStepKey(KeyframeTpl<T> const& key)
+{
+ return !IsFinite(key.inSlope) || !IsFinite(key.outSlope);
+}
+
+template<class T>
+static bool IsTooDense(KeyframeTpl<T> const& key, KeyframeTpl<T> const& previousKey, float sampleStep)
+{
+ float delta = std::abs(key.time - previousKey.time);
+
+ // epsilon is too small here, use a bigger threshold
+ return (delta - sampleStep) < -1e-5f /*-std::numeric_limits<float>::epsilon()*/;
+}
+
+template<class T>
+static bool IsDenseCurve (AnimationCurveTpl<T> const& curve)
+{
+ Assert(curve.GetKeyCount() != 0);
+
+ const float samplePerSec = 30.f;
+ const float sampleStep = 1.0f/samplePerSec;
+
+ // Remember that default curve classification is Streamed curve,
+ // which are ~8 time bigger in memory than a Dense curve( ~8x = constant cost + memory const)
+ //
+ std::pair<float, float> range = curve.GetRange();
+ float diff = range.second - range.first;
+ if(diff * samplePerSec > curve.GetKeyCount() * 8)
+ return false;
+
+ if( IsStepKey(curve.GetKey(0)) )
+ return false;
+
+ // Look for step curve, they cannot be represented by a dense curve
+ for (int i=1;i<curve.GetKeyCount();i++)
+ {
+ KeyframeTpl<T> const& previousKey = curve.GetKey(i-1);
+ KeyframeTpl<T> const& key = curve.GetKey(i);
+
+ if( IsStepKey(key) )
+ return false;
+
+ // For now if there is more key than sampling rate, revert back to streamed clip.
+ if( IsTooDense( key, previousKey, sampleStep) )
+ return false;
+ }
+
+ return true;
+}
diff --git a/Runtime/Animation/MecanimUtility.cpp b/Runtime/Animation/MecanimUtility.cpp
new file mode 100644
index 0000000..2b6da3f
--- /dev/null
+++ b/Runtime/Animation/MecanimUtility.cpp
@@ -0,0 +1,45 @@
+#include "UnityPrefix.h"
+
+#include "MecanimUtility.h"
+
+#include "Runtime/mecanim/generic/crc32.h"
+
+#include "Runtime/Utilities/File.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Allocator/MemoryManager.h"
+
+std::string BuildTransitionName(std::string srcStateName, std::string dstStateName)
+{
+ return srcStateName + " -> " + dstStateName;
+}
+
+std::string FileName(const std::string &fullpath)
+{
+ std::string fullpathCopy(fullpath);
+ ConvertSeparatorsToUnity(fullpathCopy);
+ return GetLastPathNameComponent(StandardizePathName(fullpathCopy));
+}
+std::string FileNameNoExt(const std::string &fullpath)
+{
+ return DeletePathNameExtension(FileName(fullpath));
+}
+
+unsigned int ProccessString(TOSVector& tos, std::string const& str)
+{
+ unsigned int crc32 = mecanim::processCRC32(str.c_str());
+ TOSVector::iterator it = tos.find(crc32);
+ if(it == tos.end())
+ {
+ tos.insert( std::make_pair(crc32, str) );
+ }
+ return crc32;
+}
+
+std::string FindString(TOSVector const& tos, unsigned int crc32)
+{
+ TOSVector::const_iterator it = tos.find(crc32);
+ if(it!=tos.end())
+ return it->second;
+
+ return std::string("");
+}
diff --git a/Runtime/Animation/MecanimUtility.h b/Runtime/Animation/MecanimUtility.h
new file mode 100644
index 0000000..e6bfded
--- /dev/null
+++ b/Runtime/Animation/MecanimUtility.h
@@ -0,0 +1,123 @@
+#ifndef MECANIM_UTILITY_H
+#define MECANIM_UTILITY_H
+
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Allocator/BaseAllocator.h"
+#include "Runtime/Misc/AllocatorLabels.h"
+
+#include "Runtime/mecanim/memory.h"
+#include "Runtime/Math/Simd/xform.h"
+
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+
+#include <stack>
+#include <algorithm>
+
+std::string BuildTransitionName(std::string srcStateName, std::string dstStateName);
+
+typedef std::map<mecanim::uint32_t, UnityStr> TOSVector;
+
+static inline Vector3f float4ToVector3f(math::float4 const& v)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4];
+ math::store(v, buf);
+ //return Vector3f(-buf[0],buf[1],buf[2]);
+ return Vector3f(buf[0],buf[1],buf[2]);
+}
+
+static inline Quaternionf float4ToQuaternionf(math::float4 const& q)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4];
+ math::store(math::normalize(q), buf);
+ //return Quaternionf(-buf[0],buf[1],buf[2],-buf[3]);
+ return Quaternionf(buf[0],buf[1],buf[2],buf[3]);
+}
+
+static inline Quaternionf float4ToQuaternionfNoNormalize(math::float4 const& q)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4];
+ math::store(q, buf);
+ return Quaternionf(buf[0],buf[1],buf[2],buf[3]);
+}
+
+static inline void xform2unity(math::xform const& x, Vector3f& t, Quaternionf& q, Vector3f& s)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float bufS[4];
+
+ math::store(x.s, bufS);
+
+ t = float4ToVector3f(x.t);
+ q = float4ToQuaternionf(x.q);
+ s.Set(bufS[0], bufS[1], bufS[2]);
+}
+
+static inline void xform2unityNoNormalize(math::xform const& x, Vector3f& t, Quaternionf& q, Vector3f& s)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float bufS[4];
+
+ math::store(x.s, bufS);
+
+ t = float4ToVector3f(x.t);
+ q = float4ToQuaternionfNoNormalize(x.q);
+ s.Set(bufS[0], bufS[1], bufS[2]);
+}
+
+static inline void xform2unity(math::xform const& x, Matrix4x4f& matrix)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float bufS[4];
+
+ Vector3f t;
+ Quaternionf q;
+ Vector3f s;
+
+ math::store(x.s, bufS);
+
+ t = float4ToVector3f(x.t);
+ q = float4ToQuaternionf(x.q);
+ s.Set(bufS[0], bufS[1], bufS[2]);
+ matrix.SetTRS(t, q, s);
+}
+
+
+static inline math::float4 Vector3fTofloat4(Vector3f const& v, float w = 0)
+{
+ //ATTRIBUTE_ALIGN(ALIGN4F) float buf[4] = {-v.x, v.y, v.z, 0};
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4] = {v.x, v.y, v.z, w};
+ return math::load(buf);
+}
+
+static inline math::float4 QuaternionfTofloat4(Quaternionf const& q)
+{
+ //ATTRIBUTE_ALIGN(ALIGN4F) float buf[4] = {-q.x, q.y, q.z, -q.w};
+ ATTRIBUTE_ALIGN(ALIGN4F) float buf[4] = {q.x, q.y, q.z, q.w};
+ return math::load(buf);
+}
+
+static inline math::xform xformFromUnity(Vector3f const& t, Quaternionf const& q, Vector3f const& s)
+{
+ ATTRIBUTE_ALIGN(ALIGN4F) float bufS[4];
+
+ bufS[0] = s.x; bufS[1] = s.y; bufS[2] = s.z; bufS[3] = 1.f;
+ return math::xform(Vector3fTofloat4(t), QuaternionfTofloat4(q), math::load(bufS));
+}
+
+std::string FileName(const std::string &fullpath);
+std::string FileNameNoExt(const std::string &fullpath);
+
+unsigned int ProccessString(TOSVector& tos, std::string const& str);
+std::string FindString(TOSVector const& tos, unsigned int crc32);
+template <typename T> inline T* CopyBlob(T const& data, mecanim::memory::Allocator& allocator, size_t& size)
+{
+ BlobWrite::container_type blob;
+ BlobWrite blobWrite (blob, kNoTransferInstructionFlags, kBuildNoTargetPlatform);
+ blobWrite.Transfer( const_cast<T&>(data), "Base");
+
+ UInt8* ptr = reinterpret_cast<UInt8*>(allocator.Allocate(blob.size(), ALIGN_OF(T)));
+ if(ptr != 0)
+ memcpy(ptr, blob.begin(), blob.size());
+ size = blob.size();
+ return reinterpret_cast<T*>(ptr);
+}
+
+#endif
diff --git a/Runtime/Animation/Motion.cpp b/Runtime/Animation/Motion.cpp
new file mode 100644
index 0000000..6a30490
--- /dev/null
+++ b/Runtime/Animation/Motion.cpp
@@ -0,0 +1,28 @@
+#include "UnityPrefix.h"
+#include "Motion.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "Runtime/Misc/UserList.h"
+
+Motion::Motion (MemLabelId label, ObjectCreationMode mode) : Super (label, mode), m_ObjectUsers(this)
+{ }
+
+Motion::~Motion ()
+{
+
+}
+
+
+void Motion::NotifyObjectUsers(const MessageIdentifier& msg)
+{
+ m_ObjectUsers.SendMessage(msg);
+}
+
+void Motion::AddObjectUser( UserList& user )
+{
+ m_ObjectUsers.AddUser(user);
+}
+
+IMPLEMENT_CLASS (Motion)
+
diff --git a/Runtime/Animation/Motion.h b/Runtime/Animation/Motion.h
new file mode 100644
index 0000000..bffc91e
--- /dev/null
+++ b/Runtime/Animation/Motion.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Misc/UserList.h"
+#include "Runtime/Math/Vector3.h"
+
+class MessageIdentifier;
+class AnimationClip;
+
+class Motion : public NamedObject
+{
+public:
+ REGISTER_DERIVED_ABSTRACT_CLASS (Motion, NamedObject)
+
+ Motion (MemLabelId label, ObjectCreationMode mode);
+
+ void NotifyObjectUsers(const MessageIdentifier& msg);
+ void AddObjectUser( UserList& user );
+
+#if UNITY_EDITOR
+ virtual float GetAverageDuration() = 0;
+ virtual float GetAverageAngularSpeed() = 0;
+ virtual Vector3f GetAverageSpeed() = 0;
+ virtual float GetApparentSpeed() = 0;
+
+ virtual bool ValidateIfRetargetable(bool showWarning = true) = 0;
+
+ virtual bool IsLooping() = 0 ;
+
+ virtual bool IsAnimatorMotion()const = 0;
+ virtual bool IsHumanMotion() = 0;
+
+#endif
+
+ virtual void AddUser(UserList& dependencies) { dependencies.AddUser(GetUserList ());}
+
+ UserList& GetUserList () { return m_ObjectUsers; }
+
+private:
+
+ UserList m_ObjectUsers;
+};
diff --git a/Runtime/Animation/NewAnimationTrack.cpp b/Runtime/Animation/NewAnimationTrack.cpp
new file mode 100644
index 0000000..0489f59
--- /dev/null
+++ b/Runtime/Animation/NewAnimationTrack.cpp
@@ -0,0 +1,80 @@
+#include "UnityPrefix.h"
+#if UNITY_EDITOR
+#include "NewAnimationTrack.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/Graphics/Transform.h"
+
+using namespace std;
+
+enum { kModifiesRotation = 1 << 0, kModifiesScale = 1 << 1 };
+
+vector<string> NewAnimationTrack::GetCurves ()
+{
+ vector<string> curves;
+ curves.reserve (m_Curves.size ());
+ for (Curves::iterator i=m_Curves.begin ();i!=m_Curves.end ();i++)
+ curves.push_back (i->attributeName);
+ return curves;
+}
+
+NewAnimationTrack::NewAnimationTrack (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_ClassID = 0;
+}
+
+NewAnimationTrack::~NewAnimationTrack ()
+{}
+
+/*
+template<class T> inline
+float GetValue (Object& o, int offset)
+{
+ return *reinterpret_cast<T*> (reinterpret_cast<char*> (&o) + offset);
+}
+
+bool NewAnimationTrack::ExtractFloatValue (Object* src, const TypeTree* value, float* f)
+{
+ if (value == NULL)
+ return false;
+ if (value->m_ByteOffset == -1)
+ return false;
+
+ if (value->m_Type == "float")
+ {
+ if (src && f)
+ *f = GetValue<float> (*src, value->m_ByteOffset);
+ return true;
+ }
+ else if (value->m_Type == "bool")
+ {
+ if (src && f)
+ *f = GetValue<bool> (*src, value->m_ByteOffset);
+ return true;
+ }
+ else
+ return false;
+}
+*/
+template<class TransferFunction>
+void NewAnimationTrack::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER (m_Curves);
+ TRANSFER (m_ClassID);
+}
+
+template<class TransferFunction>
+void NewAnimationTrack::Channel::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (byteOffset);
+ TRANSFER (curve);
+ TRANSFER (attributeName);
+}
+
+IMPLEMENT_CLASS (NewAnimationTrack)
+IMPLEMENT_OBJECT_SERIALIZE (NewAnimationTrack)
+#endif
diff --git a/Runtime/Animation/NewAnimationTrack.h b/Runtime/Animation/NewAnimationTrack.h
new file mode 100644
index 0000000..a50ee57
--- /dev/null
+++ b/Runtime/Animation/NewAnimationTrack.h
@@ -0,0 +1,74 @@
+
+/// BACKWARDS COMPATIBILITY ONLY
+#if UNITY_EDITOR
+#ifndef NEWANIMATIONTRACK_H
+#define NEWANIMATIONTRACK_H
+
+#include "BaseAnimationTrack.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+/// The animationTrack2 class is currently specialized to handling only
+/// transformcomponents. It will be expanded to animate any data that is serialized.
+
+class NewAnimationTrack : public BaseAnimationTrack
+{
+ public:
+
+ REGISTER_DERIVED_CLASS (NewAnimationTrack, BaseAnimationTrack)
+ DECLARE_OBJECT_SERIALIZE (NewAnimationTrack)
+
+ NewAnimationTrack (MemLabelId label, ObjectCreationMode mode);
+ // ~NewAnimationTrack (); declared-by-macro
+ /// The attributename is generated from the serialization system
+ /// and is the path to the property. "." is used as the path seperator.
+ /// It is supposed to be similar how you access the variable in C++
+ /// A transformcomponent that transfers a Vector3 using TRANSFER (m_LocalPosition)
+ /// and a Vector3 that transfers using TRANSFER (x)
+ /// would have a attribute name "m_LocalPosition.x"
+/*
+ /// Returns the curve for an attribute.
+ Channel* GetChannel (const std::string& attributeName);
+ int GetChannelCount () { m_Curves.size (); }
+ Channel& GetChannelAtIndex (int index) { return m_Curves[index]; }
+*/
+
+ /// Inserts a curve for an attribute always returns true
+// bool InsertCurve (const std::string& attributeName, const AnimationCurve& curve);
+// bool RemoveCurve (const std::string& attributeName);
+ AnimationCurve* GetCurve (const std::string& attributeName);
+ std::vector<std::string> GetCurves ();
+
+// virtual void SampleAnimation (Object& o, float time, int /*wrapmode*/);
+// virtual std::pair<float, float> GetRange () const;
+// virtual std::pair<float, float> GetPlayableRange () const;
+// int GetAnimationClassID () const { return m_ClassID; }
+// void SetAnimationClassID (int classID) { m_ClassID = classID; }
+
+ struct Channel
+ {
+ int byteOffset;
+// int type;
+ AnimationCurve curve;
+ UnityStr attributeName;
+
+ bool operator == (const string& name) { return attributeName == name; }
+
+ DECLARE_SERIALIZE (Channel)
+ };
+
+ /// Returns whether the animation system supports the value!
+ /// if src and f are non-null extracts the value into f.
+// static bool ExtractFloatValue (Object* src, const TypeTree* value, float* f);
+ typedef std::vector<Channel> Curves;
+
+ Curves m_Curves;
+ int m_ClassID;
+// int m_Optimizations;
+
+// void RebuildByteOffsets ();
+
+ friend class Animation;
+};
+
+#endif
+#endif
diff --git a/Runtime/Animation/OptimizeTransformHierarchy.cpp b/Runtime/Animation/OptimizeTransformHierarchy.cpp
new file mode 100644
index 0000000..85e30c5
--- /dev/null
+++ b/Runtime/Animation/OptimizeTransformHierarchy.cpp
@@ -0,0 +1,303 @@
+#include "UnityPrefix.h"
+
+#include "OptimizeTransformHierarchy.h"
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/Animation/AvatarBuilder.h"
+#include "Runtime/mecanim/animation/avatar.h"
+#include "Runtime/mecanim/skeleton/skeleton.h"
+
+#include <map>
+
+using namespace std;
+using namespace mecanim::animation;
+using namespace mecanim::skeleton;
+using namespace mecanim;
+
+// static function forward declarations
+static void FlattenHierarchy (GameObject& gameObject);
+static void FlattenHierarchyRecurse (Transform& transform, Transform& root);
+
+void OptimizeTransformHierarchy(GameObject& root, const UnityStr* exposedTransforms, size_t exposedTransformCount)
+{
+ Animator* animator = root.QueryComponent(Animator);
+ if (!animator)
+ return;
+
+ if(!animator->IsOptimizable())
+ return;
+
+ Transform* rootTransform = animator->GetAvatarRoot();
+ if(!rootTransform)
+ return;
+
+ GameObject& effectiveRoot = rootTransform->GetGameObject();
+
+ // 1. Take care of the SkinnedMeshRenderers, so that the will work without the transform hierarchy.
+ dynamic_array<SkinnedMeshRenderer*> skins (kMemTempAlloc);
+ GetComponentsInChildren(effectiveRoot, true, ClassID(SkinnedMeshRenderer), reinterpret_cast<dynamic_array<Unity::Component*>&>(skins));
+ for (int i=0;i<skins.size();i++)
+ {
+ SkinnedMeshRenderer& skin = *skins[i];
+
+ // The root bone of the SkinnedMeshRenderer will always be itself in optimized mode.
+ // We'll directly set the correct value to it through the transform binding.
+ if (skin.GetRootBone())
+ {
+ const Transform& skinRoot = *skin.GetRootBone();
+ Transform& skinTransform = skin.GetComponent(Transform);
+ skinTransform.SetPositionAndRotation(skinRoot.GetPosition(), skinRoot.GetRotation());
+ // TODO: handle scale if needed later
+ }
+ skin.SetRootBone(NULL);
+
+ // Clear the Transform array, we'll use skeleton indices array instead.
+ skin.SetBones(dynamic_array<PPtr<Transform> > ());
+ }
+
+ // 2. Flatten the transform hierarchy
+ FlattenHierarchy (effectiveRoot);
+
+ // 3. Remove the unnecessary transforms
+ // Here, we can safely remove transforms that are:
+ // a) human bones, because the Avatar will take care of them
+ // b) referred by SkinnedMeshRenderer, because the SkinnedMeshRenderer can work with skeleton indices.
+ UNITY_VECTOR(kMemTempAlloc, UnityStr) exposedTransformNames(exposedTransformCount);
+ for (int i = 0; i < exposedTransformCount; i++)
+ exposedTransformNames[i] = GetLastPathNameComponent(exposedTransforms[i].c_str(), exposedTransforms[i].size());
+ RemoveUnnecessaryTransforms (root, NULL,
+ exposedTransformCount ? &(exposedTransformNames[0]) : NULL,
+ exposedTransformCount, false);
+
+ // Finally, set the Animator to be optimized mode.
+ animator->SetHasTransformHierarchy(false);
+}
+
+void DeoptimizeTransformHierarchy(Unity::GameObject& root)
+{
+ Animator* animator = root.QueryComponent(Animator);
+ if (!animator)
+ return;
+
+ if(!animator->IsOptimizable())
+ return;
+
+ Transform* avatarRoot = animator->GetAvatarRoot();
+ if(!avatarRoot)
+ return;
+ GameObject& effectiveRoot = avatarRoot->GetGameObject();
+
+ // 1. Figure out the skeletonPaths from the unstripped avatar
+ const Avatar& unstrippedAvatar = *animator->GetAvatar();
+ const TOSVector& tos = unstrippedAvatar.GetTOS();
+ const AvatarConstant* avatarConstant = unstrippedAvatar.GetAsset();
+ const Skeleton& skeleton = *avatarConstant->m_AvatarSkeleton;
+ const SkeletonPose& skeletonPose = *avatarConstant->m_DefaultPose;
+
+ UNITY_VECTOR(kMemTempAlloc, UnityStr) skeletonPaths;
+ for (int i = 0; i < skeleton.m_Count; i++)
+ {
+ UnityStr path = "";
+ TOSVector::const_iterator it = tos.find(skeleton.m_ID[i]);
+ if (it != tos.end())
+ path = it->second;
+ skeletonPaths.push_back(path.c_str());
+ }
+
+ // 2. Restore the original transform hierarchy
+ // Prerequisite: skeletonPaths follow pre-order traversal
+ Transform& rootTransform = effectiveRoot.GetComponent(Transform);
+ for (int i = 1; i < skeletonPaths.size(); i++) // start from 1, skip the root transform because it will always be there.
+ {
+ const UnityStr& unflattenPath = skeletonPaths[i];
+ UnityStr transformName = GetLastPathNameComponent(unflattenPath);
+ Transform* curTransform = FindTransformWithName(&rootTransform, transformName.c_str());
+ if (curTransform == NULL)
+ {
+ // Create a new GameObject with just transform component
+ GameObject& go = CreateGameObjectWithHideFlags (transformName, true, 0, "Transform", NULL);
+ curTransform = go.QueryComponent(Transform);
+ }
+
+ // insert it at the right position of the hierarchy
+ Transform* parentTransform = &rootTransform;
+ UnityStr parentPath = DeleteLastPathNameComponent(unflattenPath);
+ if (parentPath.length() > 0)
+ parentTransform = FindRelativeTransformWithPath(rootTransform, parentPath.c_str());
+ curTransform->SetParent(parentTransform);
+
+ Vector3f t, s; Quaternionf q;
+ xform2unityNoNormalize(skeletonPose.m_X[i], t, q, s);
+ curTransform->SetLocalPositionWithoutNotification(t);
+ curTransform->SetLocalRotationWithoutNotification(q);
+ curTransform->SetLocalScaleWithoutNotification(s);
+ }
+
+ // 3. Restore the values in SkinnedMeshRenderer
+ dynamic_array<SkinnedMeshRenderer*> skins (kMemTempAlloc);
+ GetComponentsInChildren(effectiveRoot, true, ClassID(SkinnedMeshRenderer), reinterpret_cast<dynamic_array<Unity::Component*>&>(skins));
+ for (int i = 0; i < skins.size(); i++)
+ {
+ SkinnedMeshRenderer& skin = *skins[i];
+
+ // a. root bone
+ Transform* rootBoneTransform = NULL;
+ BindingHash rootPathHash = 0;
+ const Mesh* mesh = skin.GetMesh();
+ if (mesh)
+ rootPathHash = mesh->GetRootBonePathHash();
+ if (rootPathHash)
+ {
+ int skeletonIndex = mecanim::skeleton::SkeletonFindNode(&skeleton, rootPathHash);
+ const UnityStr& rootPath = skeletonPaths[skeletonIndex];
+ rootBoneTransform = FindRelativeTransformWithPath(rootTransform, rootPath.c_str());
+ }
+ if (rootBoneTransform != skin.QueryComponent(Transform))
+ skin.SetRootBone(rootBoneTransform);
+
+ // b. skeleton
+ if (mesh)
+ {
+ const dynamic_array<BindingHash>& bonePathHashes = mesh->GetBonePathHashes();
+ dynamic_array<PPtr<Transform> > boneTransforms;
+ boneTransforms.resize_initialized (bonePathHashes.size());
+ for (int j = 0; j < boneTransforms.size(); j++)
+ {
+ BindingHash bonePathHash = bonePathHashes[j];
+ int boneIndexInUnstrippedSkeleton = SkeletonFindNode(&skeleton, bonePathHash);
+ AssertIf(boneIndexInUnstrippedSkeleton == -1);
+ const UnityStr& bonePath = skeletonPaths[boneIndexInUnstrippedSkeleton];
+ Transform* boneTransform = FindRelativeTransformWithPath(rootTransform, bonePath.c_str());
+ boneTransforms[j] = boneTransform;
+ }
+ skin.SetBones(boneTransforms);
+ }
+ }
+
+ // 4. Animator
+ animator->SetHasTransformHierarchy(true);
+}
+
+static void FlattenHierarchyRecurse (Transform& transform, Transform& root)
+{
+ while (transform.GetChildrenCount () > 0)
+ {
+ Transform& child = transform.GetChild (0);
+ child.SetParent (&root);
+ FlattenHierarchyRecurse (child, root);
+ }
+}
+
+static void FlattenHierarchy (GameObject& gameObject)
+{
+ Transform& root = gameObject.GetComponent(Transform);
+ int size = root.GetChildrenCount ();
+ dynamic_array<Transform*> children(size, kMemTempAlloc);
+ for (int i=0;i < size;++i)
+ children[i] = &root.GetChild(i);
+
+ for (int i=0;i < size;++i)
+ FlattenHierarchyRecurse (*children[i], root);
+}
+
+void RemoveUnnecessaryTransforms (Unity::GameObject& gameObject, const HumanDescription* human, const UnityStr* exposedTransforms, size_t exposedTransformCount, bool doKeepSkeleton)
+{
+ // false : unnecessary
+ map<Transform*, bool> transformToMark;
+ Transform& rootTransform = gameObject.GetComponent(Transform);
+
+ // 1. Initialize the map
+ dynamic_array<Transform*> allTransforms (kMemTempAlloc);
+ GetComponentsInChildren(gameObject, true, ClassID(Transform), reinterpret_cast<dynamic_array<Unity::Component*>&>(allTransforms));
+ for (int i=0; i<allTransforms.size(); i++)
+ {
+ Transform* transform = allTransforms[i];
+ bool isNecessary = (transform->GetGameObject().GetComponentCount() > 1);
+ transformToMark.insert(pair<Transform*, bool>(transform, isNecessary));
+ }
+
+ // 2. Handle human bones
+ if (human)
+ {
+ for (int i=0; i<allTransforms.size(); i++)
+ {
+ Transform* transform = allTransforms[i];
+ map<Transform*, bool>::iterator it = transformToMark.find(transform);
+ AssertIf(it == transformToMark.end());
+
+ // human bones should not be removed
+ if (std::find_if(human->m_Human.begin(), human->m_Human.end(), FindBoneName( transform->GetName() )) != human->m_Human.end() )
+ it->second = true;
+ else if (human->m_RootMotionBoneName.length() > 0)
+ {
+ // root bone should not be removed (for Humanoid & Generic root motion)
+ if (human->m_RootMotionBoneName.compare(transform->GetName()) == 0)
+ it->second = true;
+ }
+ }
+ }
+
+ // 3. Handle exposed transforms
+ for (int i=0; i<exposedTransformCount; i++)
+ {
+ const UnityStr& path = exposedTransforms[i];
+ Transform* transform = FindRelativeTransformWithPath(rootTransform, path.c_str());
+ map<Transform*, bool>::iterator it = transformToMark.find(transform);
+ AssertIf(it == transformToMark.end());
+ it->second = true;
+ }
+
+ // 4. Handle SkinnedMeshRenderers
+ if (doKeepSkeleton)
+ {
+ dynamic_array<SkinnedMeshRenderer*> skins (kMemTempAlloc);
+ GetComponentsInChildren(gameObject, true, ClassID(SkinnedMeshRenderer), reinterpret_cast<dynamic_array<Unity::Component*>&>(skins));
+
+ for (int i=0; i<skins.size(); i++)
+ {
+ const SkinnedMeshRenderer& skin = *skins[i];
+ const dynamic_array<PPtr<Transform> >& bones = skin.GetBones();
+ for (int j=0; j<bones.size(); j++)
+ {
+ Transform* transform = bones[j];
+ map<Transform*, bool>::iterator it = transformToMark.find(transform);
+ AssertIf(it == transformToMark.end());
+ it->second = true;
+ }
+ }
+ }
+
+ // 5. Handle parent transforms
+ for (int i=allTransforms.size()-1; i>=0; i--)
+ {
+ Transform* transform = allTransforms[i];
+ map<Transform*, bool>::const_iterator it = transformToMark.find(transform);
+ AssertIf(it == transformToMark.end());
+ if (!it->second)
+ continue;
+
+ Transform* parent = transform->GetParent();
+ if (!parent)
+ continue;
+ map<Transform*, bool>::iterator itParent = transformToMark.find(parent);
+ AssertIf(itParent == transformToMark.end());
+ itParent->second = true;
+ }
+
+ // Remove unnecessary transforms
+ for (int i=allTransforms.size()-1; i>=0; i--)
+ {
+ Transform* transform = allTransforms[i];
+ map<Transform*, bool>::const_iterator it = transformToMark.find(transform);
+ AssertIf(it == transformToMark.end());
+ if (it->second)
+ continue;
+ DestroyObjectHighLevel(&(transform->GetGameObject()));
+ }
+}
+
diff --git a/Runtime/Animation/OptimizeTransformHierarchy.h b/Runtime/Animation/OptimizeTransformHierarchy.h
new file mode 100644
index 0000000..fcf5ef2
--- /dev/null
+++ b/Runtime/Animation/OptimizeTransformHierarchy.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Allocator/STLAllocator.h"
+
+/// Forward declaration
+class Avatar;
+struct HumanDescription;
+
+/// Optimize the transform hierarchy of the GameObject.
+///
+/// Briefly, it will:
+/// 1. Flatten the Transform hierarchy.
+/// 2. Remove all the Transforms which are not necessary.
+/// 3. The SkinnedMeshRenderers will then use skeleton indices to query bone matrices instead of using Transforms.
+void OptimizeTransformHierarchy(Unity::GameObject& root, const UnityStr* exposedTransforms = NULL, size_t exposedTransformCount = 0);
+
+/// De-optimize the transform hierarchy of the GameObject.
+void DeoptimizeTransformHierarchy(Unity::GameObject& root);
+
+/// Remove unnecessary transforms.
+///
+/// 1. If 'human' isn't NULL, the human bones will be kept.
+/// 2. All the 'exposedTransforms' will be kept.
+/// 3. If 'doKeepSkeleton' is true, the Transforms that are used by SkinnedMeshRenderers will be kept.
+///
+/// If one Transform is kept, its ancestors will also be kept.
+void RemoveUnnecessaryTransforms (Unity::GameObject& gameObject, const HumanDescription* human, const UnityStr* exposedTransforms, size_t exposedTransformCount, bool doKeepSkeleton);
+
+/// Query the useful transform paths from the transform hierarchy.
+///
+/// 1. The output paths will be relative paths to 'root'
+/// 2. If there are more than one Components attached to a GameObject, then the corresponding Transform will be regarded as *Useful*.
+template <typename Alloc>
+void GetUsefulTransformPaths (const Transform& root, const Transform& transform, std::vector<UnityStr, Alloc>& outPaths)
+{
+ for (int i=0; i<transform.GetChildrenCount(); i++)
+ {
+ const Transform& child = transform.GetChild(i);
+ const GameObject& gameObject = child.GetGameObject();
+ if (gameObject.GetComponentCount() > 1)
+ {
+ UnityStr path = CalculateTransformPath(child, &root);
+ outPaths.push_back(path);
+ }
+
+ GetUsefulTransformPaths(root, child, outPaths);
+ }
+}
diff --git a/Runtime/Animation/OptimizeTransformHierarchyTests.cpp b/Runtime/Animation/OptimizeTransformHierarchyTests.cpp
new file mode 100644
index 0000000..98d4d58
--- /dev/null
+++ b/Runtime/Animation/OptimizeTransformHierarchyTests.cpp
@@ -0,0 +1,293 @@
+#include "UnityPrefix.h"
+
+#include "OptimizeTransformHierarchy.h"
+
+#include "Runtime/Animation/CharacterTestFixture.h"
+
+#include "Runtime/Testing/Testing.h"
+#include "Runtime/Testing/TestFixtures.h"
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#if ENABLE_UNIT_TESTS
+
+using namespace Unity;
+using namespace std;
+
+SUITE (OptimizeTransformHierarchy)
+{
+ TEST_FIXTURE (CharacterTestFixture, OptimizeTransformHierarchy_Remove_All_GameObjects_With_Transform_Only)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT, GetAllChildrenCount(rootTr));
+
+ CHECK(FindRelativeTransformWithPath(rootTr, "mr1") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "mr2") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "smr1") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "smr2") != NULL);
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, OptimizeTransformHierarchy_Expose_Certain_Transforms)
+ {
+ // Arrange
+ const UnityStr exposedPaths[] = {
+ "b1/b1_1/b1_1_1",
+ "b2/b2_1",
+ };
+ const int EXPOSED_COUNT = sizeof(exposedPaths)/sizeof(UnityStr);
+ MakeCharacter(exposedPaths, EXPOSED_COUNT);
+
+ // Act
+ OptimizeTransformHierarchy(*root, exposedPaths, EXPOSED_COUNT);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT+EXPOSED_COUNT, GetAllChildrenCount(rootTr));
+
+ CHECK(FindRelativeTransformWithPath(rootTr, "mr1") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "mr2") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "smr1") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "smr2") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "b1_1_1") != NULL);
+ CHECK(FindRelativeTransformWithPath(rootTr, "b2_1") != NULL);
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, OptimizeTransformHierarchy_Flattened_Transforms_Have_Correct_TRS)
+ {
+ // Arrange
+ const UnityStr exposedPaths[] = {
+ "b1/b1_1/b1_1_1",
+ };
+ const int EXPOSED_COUNT = sizeof(exposedPaths)/sizeof(UnityStr);
+ MakeCharacter(exposedPaths, EXPOSED_COUNT);
+
+ // Act
+ OptimizeTransformHierarchy(*root, exposedPaths, EXPOSED_COUNT);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ Transform* mr1 = FindRelativeTransformWithPath(rootTr, "mr1");
+ Transform* b1_1_1 = FindRelativeTransformWithPath(rootTr, "b1_1_1");
+ CHECK(CompareApproximately(mr1->GetPosition(), Vector3f(3,1.5,0), Vector3f::epsilon));
+ CHECK(CompareApproximately(b1_1_1->GetPosition(), Vector3f(3,1,0), Vector3f::epsilon));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, OptimizeTransformHierarchy_Set_Animator_HasTransformHierarchy_False)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+
+ // Assert
+ Animator& animator = root->GetComponent(Animator);
+ CHECK(!animator.GetHasTransformHierarchy());
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, OptimizeTransformHierarchy_Set_Bones_And_RootBone_of_SkinnedMeshRenderers)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+
+ // Assert
+ dynamic_array<SkinnedMeshRenderer*> skins;
+ GetComponentsInChildren(*root, true, ClassID(SkinnedMeshRenderer), reinterpret_cast<dynamic_array<Unity::Component*>&>(skins));
+ CHECK_EQUAL(2, skins.size());
+ for (int i = 0; i < skins.size(); i++)
+ {
+ SkinnedMeshRenderer& skin = *skins[i];
+ CHECK(NULL == skin.GetRootBone());
+ CHECK_EQUAL(0, skin.GetBones().size());
+ }
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, DeoptimizeTransformHierarchy_Restore_Unstripped_Hierarchy)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+ DeoptimizeTransformHierarchy(*root);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ for (int i = 0; i < BONE_COUNT; i++)
+ CHECK(FindRelativeTransformWithPath(rootTr, BONE_ARRAY[i].path) != NULL);
+ for (int i = 0; i < MESH_RENDERER_COUNT; i++)
+ CHECK(FindRelativeTransformWithPath(rootTr, MESH_RENDERER_ARRAY[i].path) != NULL);
+ for (int i = 0; i < SKINNED_MESH_RENDERER_COUNT; i++)
+ CHECK(FindRelativeTransformWithPath(rootTr, SKINNED_MESH_RENDERER_ARRAY[i].path) != NULL);
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, DeoptimizeTransformHierarchy_Set_Bones_And_RootBone_of_SkinnedMeshRenderers)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+ DeoptimizeTransformHierarchy(*root);
+
+ // Assert
+ dynamic_array<SkinnedMeshRenderer*> skins;
+ GetComponentsInChildren(*root, true, ClassID(SkinnedMeshRenderer), reinterpret_cast<dynamic_array<Unity::Component*>&>(skins));
+ CHECK_EQUAL(2, skins.size());
+ for (int i = 0; i < skins.size(); i++)
+ {
+ SkinnedMeshRenderer* skin = skins[i];
+ string boneNames("");
+ for (int b = 0; b < skin->GetBones().size(); b++)
+ boneNames += (skin->GetBones()[b]->GetName() + string(","));
+
+ if (skin->GetName() == "smr1")
+ {
+ CHECK(skin->GetRootBone()->GetName() == string("b1"));
+ CHECK(skin->GetBones().size() == 6);
+ CHECK(boneNames.find("b1") != string::npos);
+ CHECK(boneNames.find("b1_1") != string::npos);
+ CHECK(boneNames.find("b1_1_1") != string::npos);
+ CHECK(boneNames.find("b1_2") != string::npos);
+ CHECK(boneNames.find("b1_2_1") != string::npos);
+ CHECK(boneNames.find("b1_2_2") != string::npos);
+ }
+ else if (skin->GetName() == "smr2")
+ {
+ CHECK(skin->GetRootBone() == NULL);
+ CHECK(skin->GetBones().size() == 3);
+ CHECK(boneNames.find("b2_1_1") != string::npos);
+ CHECK(boneNames.find("b2_1_2") != string::npos);
+ CHECK(boneNames.find("b2_1_2_1") != string::npos);
+ }
+ }
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, DeoptimizeTransformHierarchy_Restore_Transforms_With_Correct_TRS)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+ DeoptimizeTransformHierarchy(*root);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ Transform* tobeStrip1 = FindRelativeTransformWithPath(rootTr, "b1/b1_2/b1_2_1/tobeStrip1");
+ Transform* b1_1_1 = FindRelativeTransformWithPath(rootTr, "b1/b1_1/b1_1_1");
+ Transform* mr2 = FindRelativeTransformWithPath(rootTr, "b1/b1_2/b1_2_1/mr2");
+ Transform* smr2 = FindRelativeTransformWithPath(rootTr, "b2/b2_1/smr2");
+ CHECK(CompareApproximately(tobeStrip1->GetPosition(), Vector3f(5,5,5), Vector3f::epsilon));
+ CHECK(CompareApproximately(b1_1_1->GetPosition(), Vector3f(3,1,0), Vector3f::epsilon));
+ CHECK(CompareApproximately(mr2->GetPosition(), Vector3f(2,2.5,0), Vector3f::epsilon));
+ CHECK(CompareApproximately(smr2->GetPosition(), Vector3f(9,9,9), Vector3f::epsilon));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, DeoptimizeTransformHierarchy_Set_Animator_HasTransformHierarchy_True)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ OptimizeTransformHierarchy(*root);
+ DeoptimizeTransformHierarchy(*root);
+
+ // Assert
+ Animator& animator = root->GetComponent(Animator);
+ CHECK(animator.GetHasTransformHierarchy());
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, RemoveUnnecessaryTransforms_Keep_Skeleton)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ RemoveUnnecessaryTransforms(*root, NULL, NULL, 0, true);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(BONE_COUNT+MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT-3, GetAllChildrenCount(rootTr));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, RemoveUnnecessaryTransforms_Not_Keep_Skeleton)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ RemoveUnnecessaryTransforms(*root, NULL, NULL, 0, false);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(BONE_COUNT+MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT-3-4, GetAllChildrenCount(rootTr));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, RemoveUnnecessaryTransforms_Consider_HumanDescription)
+ {
+ // Arrange
+ MakeCharacter();
+ HumanBone hb;
+ hb.m_BoneName = "b1_2_2";
+ HumanDescription hd;
+ hd.m_Human.push_back(hb);
+
+ // Act
+ RemoveUnnecessaryTransforms(*root, &hd, NULL, 0, false);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(BONE_COUNT+MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT-3-3, GetAllChildrenCount(rootTr));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, RemoveUnnecessaryTransforms_Expose_Certain_Transforms)
+ {
+ // Arrange
+ MakeCharacter();
+ const UnityStr exposedPaths[] = {
+ "b1/b1_2/b1_2_2",
+ "b2/b2_1/b2_1_2",
+ };
+ const int EXPOSED_COUNT = sizeof(exposedPaths)/sizeof(UnityStr);
+
+ // Act
+ RemoveUnnecessaryTransforms(*root, NULL, exposedPaths, EXPOSED_COUNT, false);
+
+ // Assert
+ Transform& rootTr = root->GetComponent(Transform);
+ CHECK_EQUAL(BONE_COUNT+MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT-3-2, GetAllChildrenCount(rootTr));
+ }
+
+ TEST_FIXTURE (CharacterTestFixture, GetUsefulTransformPaths)
+ {
+ // Arrange
+ MakeCharacter();
+
+ // Act
+ Transform& rootTr = root->GetComponent(Transform);
+ UNITY_VECTOR(kMemTempAlloc, UnityStr) outPaths;
+ GetUsefulTransformPaths(rootTr, rootTr, outPaths);
+
+ // Assert
+ CHECK_EQUAL(MESH_RENDERER_COUNT+SKINNED_MESH_RENDERER_COUNT, outPaths.size());
+ for (int i = 0; i < MESH_RENDERER_COUNT; ++i)
+ CHECK(std::find(outPaths.begin(), outPaths.end(), MESH_RENDERER_ARRAY[i].path) != outPaths.end());
+ for (int i = 0; i < SKINNED_MESH_RENDERER_COUNT; ++i)
+ CHECK(std::find(outPaths.begin(), outPaths.end(), SKINNED_MESH_RENDERER_ARRAY[i].path) != outPaths.end());
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/Runtime/Animation/PPtrKeyframes.h b/Runtime/Animation/PPtrKeyframes.h
new file mode 100644
index 0000000..1620d7c
--- /dev/null
+++ b/Runtime/Animation/PPtrKeyframes.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Runtime/BaseClasses/BaseObject.h"
+
+struct PPtrKeyframe
+{
+ float time;
+ PPtr<Object> value;
+
+ DECLARE_SERIALIZE(PPtrKeyframe)
+};
+typedef dynamic_array<PPtrKeyframe> PPtrKeyframes;
diff --git a/Runtime/Animation/RuntimeAnimatorController.cpp b/Runtime/Animation/RuntimeAnimatorController.cpp
new file mode 100644
index 0000000..c92e976
--- /dev/null
+++ b/Runtime/Animation/RuntimeAnimatorController.cpp
@@ -0,0 +1,66 @@
+#include "UnityPrefix.h"
+
+#include "RuntimeAnimatorController.h"
+#include "AnimationSetBinding.h"
+
+
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/Blobification/BlobWrite.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "AnimationClip.h"
+
+#if UNITY_EDITOR
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+#include "Runtime/Mono/MonoManager.h"
+#endif
+
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+#include "Runtime/Scripting/Scripting.h"
+
+IMPLEMENT_OBJECT_SERIALIZE (RuntimeAnimatorController)
+IMPLEMENT_CLASS(RuntimeAnimatorController)
+INSTANTIATE_TEMPLATE_TRANSFER(RuntimeAnimatorController)
+
+
+RuntimeAnimatorController::RuntimeAnimatorController(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode),
+ m_ObjectUsers(this),
+ m_DependencyList(this)
+{
+
+}
+
+RuntimeAnimatorController::~RuntimeAnimatorController()
+{
+ NotifyObjectUsers( kDidModifyAnimatorController );
+}
+
+
+template<class TransferFunction>
+void RuntimeAnimatorController::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+}
+
+
+void RuntimeAnimatorController::NotifyObjectUsers(const MessageIdentifier& msg)
+{
+ m_ObjectUsers.SendMessage(msg);
+}
+
+void RuntimeAnimatorController::RegisterAnimationClips()
+{
+ AnimationClipVector clips = GetAnimationClipsToRegister();
+ m_DependencyList.Clear();
+ m_DependencyList.Reserve(clips.size()); // Reserve space just for niceness
+ for(int i = 0 ; i < clips.size() ; i++)
+ {
+ AnimationClip* clip = clips[i];
+ if (clip)
+ {
+ // We could do this either way, adding is symmetrical
+ clip->GetUserList().AddUser(m_DependencyList);
+ }
+ }
+}
+
diff --git a/Runtime/Animation/RuntimeAnimatorController.h b/Runtime/Animation/RuntimeAnimatorController.h
new file mode 100644
index 0000000..058891a
--- /dev/null
+++ b/Runtime/Animation/RuntimeAnimatorController.h
@@ -0,0 +1,81 @@
+#ifndef AVATARCONTROLLER_H
+#define AVATARCONTROLLER_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "Runtime/mecanim/memory.h"
+#include "Runtime/Animation/MecanimUtility.h"
+#include "Runtime/mecanim/animation/avatar.h"
+
+#include "Runtime/Misc/UserList.h"
+
+template<class T>
+class PPtr;
+class AnimationClip;
+class StateMachine;
+class AvatarMask;
+
+class RuntimeAnimatorController;
+
+typedef std::vector<PPtr<AnimationClip> > AnimationClipVector;
+
+namespace UnityEngine
+{
+ namespace Animation
+ {
+ struct AnimationSetBindings;
+ }
+}
+
+namespace mecanim
+{
+ namespace animation
+ {
+ struct ControllerConstant;
+ }
+}
+
+class RuntimeAnimatorController : public NamedObject
+{
+
+public:
+
+ REGISTER_DERIVED_ABSTRACT_CLASS (RuntimeAnimatorController, NamedObject)
+ DECLARE_OBJECT_SERIALIZE (RuntimeAnimatorController)
+
+ RuntimeAnimatorController(MemLabelId label, ObjectCreationMode mode);
+
+
+ static void InitializeClass ();
+ static void CleanupClass () {}
+
+ virtual mecanim::animation::ControllerConstant* GetAsset() = 0 ;
+ virtual UnityEngine::Animation::AnimationSetBindings* GetAnimationSetBindings() = 0 ;
+
+ virtual AnimationClipVector GetAnimationClips() const = 0;
+
+ virtual std::string StringFromID(unsigned int ID) const = 0;
+
+ void AddObjectUser( UserList& user ) { m_ObjectUsers.AddUser(user); }
+ void AddObjectUser( UserListNode& user ) { m_ObjectUsers.AddUser(user); }
+
+ void NotifyObjectUsers(const MessageIdentifier& msg);
+ UserList& GetUserList () { return m_ObjectUsers; }
+
+
+protected:
+
+ virtual void RegisterAnimationClips() ;
+
+ UserList m_ObjectUsers; // for animatorControllers
+ UserList m_DependencyList; // for animationclips
+
+private:
+
+ virtual AnimationClipVector GetAnimationClipsToRegister() const = 0;
+
+
+};
+
+
+#endif
diff --git a/Runtime/Animation/ScriptBindings/Animations.txt b/Runtime/Animation/ScriptBindings/Animations.txt
new file mode 100644
index 0000000..ddeb57e
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/Animations.txt
@@ -0,0 +1,721 @@
+C++RAW
+
+#include "UnityPrefix.h"
+#include "Runtime/Animation/Animation.h"
+#include "Runtime/Animation/AnimationClip.h"
+#include "Runtime/Animation/AnimationManager.h"
+#include "Runtime/Animation/AnimationState.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Animation/Motion.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Animation/AnimationCurveUtility.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Scripting/ScriptingManager.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/ScriptingExportUtility.h"
+#include "Runtime/Scripting/Backend/ScriptingTypeRegistry.h"
+#include "Runtime/Scripting/ScriptingObjectWithIntPtrField.h"
+#include "Runtime/mecanim/human/human.h"
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/Animation/AnimationUtility.h"
+#include "Runtime/Scripting/Scripting.h"
+
+using namespace Unity;
+
+CSRAW
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+// Determines how time is treated outside of the keyframed range of an [[AnimationClip]] or [[AnimationCurve]].
+ENUM WrapMode
+ // When time reaches the end of the animation clip, the clip will automatically stop playing and time will be reset to beginning of the clip.
+ Once = 1,
+
+ // When time reaches the end of the animation clip, time will continue at the beginning.
+ Loop = 2,
+
+ // When time reaches the end of the animation clip, time will ping pong back between beginning and end.
+ PingPong = 4,
+
+ // Reads the default repeat mode set higher up.
+ Default = 0,
+
+ // Plays back the animation. When it reaches the end, it will keep playing the last frame and never stop playing.
+ ClampForever = 8,
+
+ //*undocumented*
+ Clamp = 1,
+END
+
+// AnimationEvent lets you call a script function similar to SendMessage as part of playing back an animation.
+CSRAW [StructLayout (LayoutKind.Sequential)]
+CLASS AnimationEvent
+ CSRAW [NotRenamed]
+ CSRAW internal IntPtr m_Ptr;
+ CSRAW int m_OwnsData;
+
+ C++RAW
+
+ struct AnimationEventMono
+ {
+ AnimationEvent* m_Ptr;
+ int m_OwnsData;
+ };
+
+ C++RAW
+ void VerifyReadOnly (ScriptingObjectWithIntPtrField<AnimationEvent>& self)
+ {
+#if ENABLE_MONO
+ if (ExtractMonoObjectData<AnimationEventMono> (self.object).m_OwnsData != 1)
+ Scripting::RaiseMonoException("AnimationEvents sent by an Animation Event callback may not modify the AnimationEvent data");
+#endif
+ }
+
+ C++RAW
+ inline AnimationEvent* GetAnimationEvent (ScriptingObjectWithIntPtrField<AnimationEvent>& self)
+ {
+ AnimationEvent* event = self.GetPtr();
+ if (!event)
+ Scripting::RaiseNullException("Animation Event is out of scope");
+
+ return event;
+ }
+
+ // Creates a new animation event
+ CSRAW public AnimationEvent ()
+ {
+ m_OwnsData = 1;
+ Create();
+ }
+
+ THREAD_SAFE
+ CUSTOM private void Create ()
+ {
+ self.SetPtr(new AnimationEvent());
+ }
+
+ CSRAW ~AnimationEvent ()
+ {
+ if (m_OwnsData != 0)
+ Destroy();
+ }
+
+ THREAD_SAFE
+ CUSTOM private void Destroy ()
+ {
+ delete self.GetPtr();
+ }
+
+ OBSOLETE warning Use stringParameter instead
+ CUSTOM_PROP string data { return scripting_string_new(GetAnimationEvent(self)->stringParameter); } { VerifyReadOnly(self); GetAnimationEvent(self)->stringParameter = value.AsUTF8(); }
+
+ // String parameter that is stored in the event and will be sent to the function.
+ CUSTOM_PROP string stringParameter { return scripting_string_new(GetAnimationEvent(self)->stringParameter); } { VerifyReadOnly(self); GetAnimationEvent(self)->stringParameter = value.AsUTF8(); }
+
+ // Float parameter that is stored in the event and will be sent to the function.
+ CUSTOM_PROP float floatParameter { return GetAnimationEvent(self)->floatParameter; } { VerifyReadOnly(self); GetAnimationEvent(self)->floatParameter = value; }
+
+ // int parameter that is stored in the event and will be sent to the function.
+ CUSTOM_PROP int intParameter { return GetAnimationEvent(self)->intParameter; } { VerifyReadOnly(self); GetAnimationEvent(self)->intParameter = value; }
+
+ // Object reference parameter that is stored in the event and will be sent to the function.
+ CUSTOM_PROP Object objectReferenceParameter { return Scripting::ScriptingWrapperFor(GetAnimationEvent(self)->objectReferenceParameter); } { VerifyReadOnly(self); GetAnimationEvent(self)->objectReferenceParameter = (Object*)value; }
+
+ // The name of the function that will be called.
+ CUSTOM_PROP string functionName { return scripting_string_new(GetAnimationEvent(self)->functionName); } { VerifyReadOnly(self);GetAnimationEvent(self)->functionName = value.AsUTF8(); }
+
+ // The time at which the event will be fired off.
+ CUSTOM_PROP float time { return GetAnimationEvent(self)->time; } { VerifyReadOnly(self); GetAnimationEvent(self)->time = value; }
+
+ // Function call options.
+ CUSTOM_PROP SendMessageOptions messageOptions { return GetAnimationEvent(self)->messageOptions; } { VerifyReadOnly(self); GetAnimationEvent(self)->messageOptions = value; }
+
+ // The animation state that fired this event (RO).
+ CUSTOM_PROP AnimationState animationState
+ {
+ return TrackedReferenceBaseToScriptingObject(GetAnimationEvent(self)->stateSender, animationState);
+ }
+
+END
+
+// Stores keyframe based animations.
+CLASS AnimationClip : Motion
+
+ // Creates a new animation clip
+ CSRAW public AnimationClip()
+ {
+ Internal_CreateAnimationClip(this);
+ }
+
+ CUSTOM private static void Internal_CreateAnimationClip ([Writable]AnimationClip self)
+ {
+ Object* animClip = NEW_OBJECT(AnimationClip);
+ animClip->Reset();
+ Scripting::ConnectScriptingWrapperToObject (self.GetScriptingObject(), animClip);
+ animClip->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
+ }
+
+ // Animation length in seconds (RO)
+ CUSTOM_PROP float length { return self->GetRange ().second; }
+
+
+ CUSTOM_PROP internal float startTime { return self->GetRange ().first; }
+ CUSTOM_PROP internal float stopTime { return self->GetRange ().second; }
+
+
+ // Frame rate at which keyframes are sampled (RO)
+ AUTO_PROP float frameRate GetSampleRate SetSampleRate
+
+
+ // Assigns the curve to animate a specific property.
+ CUSTOM void SetCurve (string relativePath, Type type, string propertyName, AnimationCurve curve)
+ {
+#if ENABLE_MONO
+ Scripting::RaiseIfNull(type);
+ MonoClass* klass = GetScriptingTypeRegistry().GetType(type);
+ MonoScript* script = NULL;
+ int classID = Scripting::GetClassIDFromScriptingClass(klass);
+ if (classID == ClassID(MonoBehaviour))
+ {
+ script = GetMonoScriptManager().FindRuntimeScript(klass);
+ if (script == NULL)
+ {
+ ErrorString("The script class couldn't be found");
+ return;
+ }
+ }
+
+ self->SetCurve(relativePath, classID, script, propertyName, curve.GetPtr(), true);
+#endif
+ }
+
+ //*undocumented*
+ AUTO void EnsureQuaternionContinuity();
+
+ // Clears all curves from the clip.
+ AUTO void ClearCurves();
+
+ // Sets the default wrap mode used in the animation state.
+ AUTO_PROP WrapMode wrapMode GetWrapMode SetWrapMode
+
+ // Adds an animation event to the clip.
+ CUSTOM void AddEvent (AnimationEvent evt)
+ {
+ Scripting::RaiseIfNull(evt.GetPtr());
+ self->AddRuntimeEvent(*GetAnimationEvent(evt));
+ }
+
+ // AABB of this Animation Clip in local space of Animation component that it is attached too.
+ AUTO_PROP Bounds localBounds GetBounds SetBounds
+
+END
+
+// A single keyframe that can be injected into an animation curve.
+STRUCT Keyframe
+ CSRAW float m_Time;
+ CSRAW float m_Value;
+ CSRAW float m_InTangent;
+ CSRAW float m_OutTangent;
+
+ CSRAW
+ #if UNITY_EDITOR
+ int m_TangentMode;
+ #endif
+
+ // Create a keyframe.
+ CSRAW public Keyframe (float time, float value)
+ {
+ m_Time = time;
+ m_Value = value;
+ m_InTangent = 0;
+ m_OutTangent = 0;
+ #if UNITY_EDITOR
+ m_TangentMode = 0;
+ #endif
+ }
+
+ // Create a keyframe.
+ CSRAW public Keyframe (float time, float value, float inTangent, float outTangent)
+ {
+ m_Time = time;
+ m_Value = value;
+ m_InTangent = inTangent;
+ m_OutTangent = outTangent;
+ #if UNITY_EDITOR
+ m_TangentMode = 0;
+ #endif
+ }
+
+ // The time of the keyframe.
+ CSRAW public float time { get { return m_Time; } set { m_Time = value; } }
+
+ // The value of the curve at keyframe.
+ CSRAW public float value { get { return m_Value; } set { m_Value = value; } }
+
+ // Describes the tangent when approaching this point from the previous point in the curve.
+ CSRAW public float inTangent { get { return m_InTangent; } set { m_InTangent = value; } }
+
+ // Describes the tangent when leaving this point towards the next point in the curve.
+ CSRAW public float outTangent { get { return m_OutTangent; } set { m_OutTangent = value; } }
+
+ // The tangent mode of the keyframe.
+ // This is used only in the editor and will always return 0 in the player.
+ CSRAW public int tangentMode
+ {
+ get {
+ #if UNITY_EDITOR
+ return m_TangentMode;
+ #else
+ return 0;
+ #endif
+ }
+ set {
+ #if UNITY_EDITOR
+ m_TangentMode = value;
+ #endif
+ }
+ }
+
+END
+
+CSRAW #pragma warning disable 414
+
+// An animation curve. Lets you add keyframes and evaluate the curve at a given time.
+C++RAW
+ static void CleanupAnimationCurve(void* animationCurve){ delete ((AnimationCurve*)animationCurve); };
+// A collection of curves form an [[AnimationClip]].
+CSRAW [StructLayout (LayoutKind.Sequential)]
+THREAD_SAFE
+CLASS AnimationCurve
+ CSRAW
+ internal IntPtr m_Ptr;
+
+ THREAD_SAFE
+ CUSTOM private void Cleanup () { CleanupAnimationCurve(self.GetPtr()); }
+
+ CSRAW
+ ~AnimationCurve()
+ {
+ Cleanup ();
+ }
+
+ // Evaluate the curve at /time/.
+ CUSTOM float Evaluate (float time)
+ {
+ return self->Evaluate(time);
+ }
+
+ // All keys defined in the animation curve.
+ CSRAW public Keyframe[] keys { get { return GetKeys(); } set { SetKeys(value); } }
+
+ // Add a new key to the curve.
+ CUSTOM int AddKey (float time, float value) { return AddKeySmoothTangents(*self, time, value); }
+
+ // Add a new key to the curve.
+ CSRAW public int AddKey (Keyframe key) { return AddKey_Internal(key); }
+
+ CUSTOM private int AddKey_Internal (Keyframe key) { return self->AddKey (key); }
+
+ // Removes the keyframe at /index/ and inserts key.
+ CUSTOM int MoveKey (int index, Keyframe key)
+ {
+ if (index >= 0 && index < self->GetKeyCount())
+ return MoveCurveKey(*self, index, key);
+ else {
+ Scripting::RaiseOutOfRangeException("");
+ return 0;
+ }
+ }
+
+ // Removes a key
+ CUSTOM void RemoveKey (int index)
+ {
+ if (index >= 0 && index < self->GetKeyCount())
+ self->RemoveKeys(self->begin() + index, self->begin() + index + 1);
+ else
+ Scripting::RaiseOutOfRangeException("");
+ }
+
+ // Retrieves the key at index (RO)
+ CSRAW public Keyframe this [int index]
+ {
+ get { return GetKey_Internal(index); }
+ }
+
+ // The number of keys in the curve (RO)
+ CUSTOM_PROP int length { return self->GetKeyCount(); }
+
+ // Replace all keyframes with the /keys/ array.
+ CUSTOM private void SetKeys (Keyframe[] keys)
+ {
+ KeyframeTpl<float>* first = Scripting::GetScriptingArrayStart<KeyframeTpl<float> > (keys);
+ self->Assign(first, first + GetScriptingArraySize(keys));
+ self->Sort();
+ }
+
+ CUSTOM private Keyframe GetKey_Internal (int index)
+ {
+ if (index >= 0 && index < self->GetKeyCount())
+ {
+ return self->GetKey(index);
+ }
+ else
+ {
+ Scripting::RaiseOutOfRangeException("");
+ return KeyframeTpl<float>();
+ }
+ }
+
+ CUSTOM private Keyframe[] GetKeys ()
+ {
+ if (self->GetKeyCount() <= 0)
+ return CreateEmptyStructArray(MONO_COMMON.keyframe);
+ return CreateScriptingArray(&self->GetKey(0), self->GetKeyCount(), MONO_COMMON.keyframe);
+ }
+
+ // Smooth the in and out tangents of the keyframe at /index/.
+ CUSTOM void SmoothTangents (int index, float weight)
+ {
+ if (index >= 0 && index < self->GetKeyCount())
+ RecalculateSplineSlope(*self, index, weight);
+ else
+ Scripting::RaiseOutOfRangeException("");
+ }
+
+ // A straight Line starting at /timeStart/, /valueStart/ and ending at /timeEnd/, /valueEnd/
+ CSRAW public static AnimationCurve Linear (float timeStart, float valueStart, float timeEnd, float valueEnd)
+ {
+ float tangent = (valueEnd - valueStart) / (timeEnd - timeStart);
+ Keyframe[] keys = { new Keyframe(timeStart, valueStart, 0.0F, tangent), new Keyframe(timeEnd, valueEnd, tangent, 0.0F) };
+ return new AnimationCurve(keys);
+ }
+
+ // An ease-in and out curve starting at /timeStart/, /valueStart/ and ending at /timeEnd/, /valueEnd/.
+ CSRAW public static AnimationCurve EaseInOut (float timeStart, float valueStart, float timeEnd, float valueEnd)
+ {
+ Keyframe[] keys = { new Keyframe(timeStart, valueStart, 0.0F, 0.0F), new Keyframe(timeEnd, valueEnd, 0.0F, 0.0F) };
+ return new AnimationCurve(keys);
+ }
+
+ // The behaviour of the animation before the first keyframe
+ CUSTOM_PROP WrapMode preWrapMode { return self->GetPreInfinity(); } { self->SetPreInfinity(value); }
+ // The behaviour of the animation after the last keyframe
+ CUSTOM_PROP WrapMode postWrapMode { return self->GetPostInfinity(); } { self->SetPostInfinity(value); }
+
+ // Creates an animation curve from arbitrary number of keyframes.
+ CSRAW public AnimationCurve (params Keyframe[] keys) { Init(keys); }
+
+ CONDITIONAL UNITY_FLASH || UNITY_WINRT
+ // *undocumented*
+ CSRAW public AnimationCurve(IntPtr nativeptr) { m_Ptr = nativeptr; }
+
+ // Creates an empty animation curve
+ CSRAW public AnimationCurve () { Init(null); }
+
+ THREAD_SAFE
+ CUSTOM private void Init (Keyframe[] keys)
+ {
+ self.SetPtr(new AnimationCurve(), CleanupAnimationCurve);
+ #if UNITY_WINRT
+ if (keys != SCRIPTING_NULL) AnimationCurve_CUSTOM_SetKeys(self.object, keys);
+ #else
+ if (keys != SCRIPTING_NULL) AnimationCurve_CUSTOM_SetKeys(self, keys);
+ #endif
+ }
+
+END
+
+CSRAW #pragma warning restore 414
+
+
+// Used by Animation.Play function.
+ENUM PlayMode
+ // Will stop all animations that were started in the same layer. This is the default when playing animations.
+ StopSameLayer = 0,
+ // Will stop all animations that were started with this component before playing
+ StopAll = 4,
+END
+
+// Used by Animation.Play function.
+ENUM QueueMode
+ // Will start playing after all other animations have stopped playing
+ CompleteOthers = 0,
+
+ // Starts playing immediately. This can be used if you just want to quickly create a duplicate animation.
+ PlayNow = 2
+END
+
+// Used by Animation.Play function.
+ENUM AnimationBlendMode
+ // Animations will be blended
+ Blend = 0,
+ // Animations will be added
+ Additive = 1
+END
+
+
+//*undocumented* - deprecated
+CSRAW public enum AnimationPlayMode { Stop = 0, Queue = 1, Mix = 2 }
+
+// This enum controlls culling of Animation component.
+ENUM AnimationCullingType
+ // Animation culling is disabled - object is animated even when offscreen.
+ AlwaysAnimate = 0,
+
+ // Animation is disabled when renderers are not visible.
+ BasedOnRenderers = 1,
+
+ // Animation is disabled when Animation::ref::localBounds are not visible.
+ BasedOnClipBounds = 2,
+
+ // Animation is disabled when Animation::ref::localBounds are not visible.
+ BasedOnUserBounds = 3
+END
+
+// The animation component is used to play back animations.
+CLASS Animation : Behaviour, IEnumerable
+ // The default animation.
+ AUTO_PTR_PROP AnimationClip clip GetClip SetClip
+
+ // Should the default animation clip (Animation.clip) automatically start playing on startup.
+ AUTO_PROP bool playAutomatically GetPlayAutomatically SetPlayAutomatically
+
+ // How should time beyond the playback range of the clip be treated?
+ AUTO_PROP WrapMode wrapMode GetWrapMode SetWrapMode
+
+ // Stops all playing animations that were started with this Animation.
+ AUTO void Stop ();
+
+ // Stops an animation named /name/.
+ CSRAW public void Stop (string name) { Internal_StopByName(name); }
+ CUSTOM private void Internal_StopByName (string name) { return self->Stop (name); }
+
+ // Rewinds the animation named /name/.
+ CSRAW public void Rewind (string name) { Internal_RewindByName(name); }
+ CUSTOM private void Internal_RewindByName (string name) { self->Rewind(name); }
+
+ // Rewinds all animations
+ AUTO void Rewind ();
+
+ // Samples animations at the current state.
+ AUTO void Sample ();
+
+ // Are we playing any animations?
+ AUTO_PROP bool isPlaying IsPlaying
+
+ // Is the animation named /name/ playing?
+ CUSTOM bool IsPlaying (string name) { return self->IsPlaying (name); }
+
+
+ // Returns the animation state named /name/.
+ CSRAW public AnimationState this [string name]
+ {
+ get { return GetState(name); }
+ }
+
+ /// *listonly*
+ CSRAW public bool Play (PlayMode mode = PlayMode.StopSameLayer) { return PlayDefaultAnimation (mode); }
+
+ // Plays animation without any blending.
+ CUSTOM bool Play (string animation, PlayMode mode = PlayMode.StopSameLayer) { return self->Play(animation, mode); }
+
+ // Fades the animation with name /animation/ in over a period of /time/ seconds and fades other animations out.
+ CUSTOM void CrossFade (string animation, float fadeLength = 0.3F, PlayMode mode = PlayMode.StopSameLayer) { self->CrossFade(animation, fadeLength, mode); }
+
+ // Blends the animation named /animation/ towards /targetWeight/ over the next /time/ seconds.
+ CUSTOM void Blend (string animation, float targetWeight = 1.0F, float fadeLength = 0.3F) { self->Blend(animation, targetWeight, fadeLength); }
+
+
+ // Cross fades an animation after previous animations has finished playing.
+ CUSTOM AnimationState CrossFadeQueued (string animation, float fadeLength = 0.3F, QueueMode queue = QueueMode.CompleteOthers, PlayMode mode = PlayMode.StopSameLayer)
+ {
+ AnimationState* as = self->QueueCrossFade(animation, fadeLength, queue, mode);
+ return TrackedReferenceBaseToScriptingObject(as,animationState);
+ }
+
+
+ // Plays an animation after previous animations has finished playing.
+ CUSTOM AnimationState PlayQueued (string animation, QueueMode queue = QueueMode.CompleteOthers, PlayMode mode = PlayMode.StopSameLayer)
+ {
+ AnimationState* as = self->QueuePlay(animation, queue, mode);
+ return TrackedReferenceBaseToScriptingObject(as, animationState);
+ }
+
+
+ // Adds a /clip/ to the animation with name /newName/.
+ CSRAW public void AddClip (AnimationClip clip, string newName) { AddClip (clip, newName, Int32.MinValue, Int32.MaxValue); }
+
+ // Adds /clip/ to the only play between /firstFrame/ and /lastFrame/. The new clip will also be added to the animation with name /newName/.
+ CUSTOM void AddClip (AnimationClip clip, string newName, int firstFrame, int lastFrame, bool addLoopFrame = false) { self->AddClip(*clip, newName, firstFrame, lastFrame, addLoopFrame); }
+
+ // Remove clip from the animation list.
+ CUSTOM void RemoveClip (AnimationClip clip) { self->RemoveClip (*clip); }
+
+ // Remove clip from the animation list.
+ CSRAW public void RemoveClip (string clipName) { RemoveClip2(clipName); }
+
+ // Get the number of clips currently assigned to this animation
+ CUSTOM int GetClipCount () { return self->GetClipCount(); }
+
+ CUSTOM private void RemoveClip2 (string clipName) { self->RemoveClip (clipName); }
+
+ CUSTOM private bool PlayDefaultAnimation (PlayMode mode) { return self->Play(mode); }
+
+ //*undocumented* deprecated
+ OBSOLETE warning use PlayMode instead of AnimationPlayMode.
+ CSRAW public bool Play (AnimationPlayMode mode) { return PlayDefaultAnimation((PlayMode)mode); }
+ //*undocumented* deprecated
+ OBSOLETE warning use PlayMode instead of AnimationPlayMode.
+ CSRAW public bool Play (string animation, AnimationPlayMode mode) { return Play(animation, (PlayMode)mode); }
+
+
+ // Synchronizes playback speed of all animations in the /layer/.
+ AUTO void SyncLayer(int layer);
+
+ //*undocumented* Documented separately
+ CSRAW public IEnumerator GetEnumerator ()
+ {
+ return new Animation.Enumerator (this);
+ }
+ //*undocumented*
+ CLASS private Enumerator : IEnumerator
+ CSRAW
+ private Animation m_Outer;
+ private int m_CurrentIndex = -1;
+
+ internal Enumerator (Animation outer) { m_Outer = outer; }
+ //*undocumented*
+ public object Current
+ {
+ get { return m_Outer.GetStateAtIndex (m_CurrentIndex); }
+ }
+
+ //*undocumented*
+ public bool MoveNext ()
+ {
+ int childCount = m_Outer.GetStateCount();
+ m_CurrentIndex++;
+ return m_CurrentIndex < childCount;
+ }
+
+ //*undocumented*
+ public void Reset () { m_CurrentIndex = -1; }
+ END
+
+ CUSTOM internal AnimationState GetState(string name)
+ {
+ AnimationState* state = self->GetState(name);
+ return TrackedReferenceBaseToScriptingObject(state, animationState);
+ }
+
+ CUSTOM internal AnimationState GetStateAtIndex (int index)
+ {
+
+ Animation& selfRef = *self;
+ if (index >= 0 || index < selfRef.GetAnimationStateCount())
+ {
+ return TrackedReferenceBaseToScriptingObject(&selfRef.GetAnimationStateAtIndex (index), animationState);
+ }
+ Scripting::RaiseMonoException("Animation State out of bounds!");
+ return SCRIPTING_NULL;
+ }
+
+ CUSTOM internal int GetStateCount () { return self->GetAnimationStateCount(); }
+
+ OBSOLETE planned Returns the animation clip named /name/.
+ CSRAW public AnimationClip GetClip (string name) {
+ AnimationState state = GetState(name);
+ if (state)
+ return state.clip;
+ else
+ return null;
+ }
+
+
+ // When turned on, animations will be executed in the physics loop. This is only useful in conjunction with kinematic rigidbodies.
+ AUTO_PROP bool animatePhysics GetAnimatePhysics SetAnimatePhysics
+
+ // When turned on, Unity might stop animating if it thinks that the results of the animation won't be visible to the user.
+ OBSOLETE warning Use cullingType instead
+ CUSTOM_PROP bool animateOnlyIfVisible
+ {
+ Animation::CullingType type = self->GetCullingType();
+ AssertMsg(type == Animation::kCulling_AlwaysAnimate || type == Animation::kCulling_BasedOnRenderers,
+ "Culling type %d cannot be converted to animateOnlyIfVisible. animateOnlyIfVisible is obsolete, please use cullingType instead.", type);
+ return type == Animation::kCulling_BasedOnRenderers;
+ }
+ {
+ self->SetCullingType(value ? Animation::kCulling_BasedOnRenderers : Animation::kCulling_AlwaysAnimate);
+ }
+
+ // Controls culling of this Animation component.
+ AUTO_PROP AnimationCullingType cullingType GetCullingType SetCullingType
+
+ // AABB of this Animation animation component in local space.
+ AUTO_PROP Bounds localBounds GetLocalAABB SetLocalAABB
+
+END
+
+// The AnimationState gives full control over animation blending.
+CLASS AnimationState : TrackedReference
+
+ // Enables / disables the animation.
+
+ AUTO_PROP bool enabled GetEnabled SetEnabled
+
+ // The weight of animation
+ AUTO_PROP float weight GetWeight SetWeight
+
+ // Wrapping mode of the animation.
+ AUTO_PROP WrapMode wrapMode GetWrapMode SetWrapMode
+
+ // The current time of the animation
+ AUTO_PROP float time GetTime SetTime
+
+ // The normalized time of the animation.
+ AUTO_PROP float normalizedTime GetNormalizedTime SetNormalizedTime
+
+ // The playback speed of the animation. 1 is normal playback speed.
+ AUTO_PROP float speed GetSpeed SetSpeed
+
+ // The normalized playback speed.
+ AUTO_PROP float normalizedSpeed GetNormalizedSpeed SetNormalizedSpeed
+
+ // The length of the animation clip in seconds.
+ AUTO_PROP float length GetLength
+
+ // The layer of the animation. When calculating the final blend weights, animations in higher layers will get their weights
+ AUTO_PROP int layer GetLayer SetLayer
+
+ // The clip that is being played by this animation state.
+ AUTO_PTR_PROP AnimationClip clip GetClip
+
+ // Adds a transform which should be animated. This allows you to reduce the number of animations you have to create.
+ CUSTOM void AddMixingTransform (Transform mix, bool recursive = true) { self->AddMixingTransform(*mix, recursive); }
+
+ // Removes a transform which should be animated.
+ CUSTOM void RemoveMixingTransform (Transform mix) { self->RemoveMixingTransform(*mix); }
+
+ // The name of the animation
+ CUSTOM_PROP string name { return scripting_string_new(self->GetName()); } { self->SetName(value.AsUTF8()); }
+
+ // Which blend mode should be used?
+ AUTO_PROP AnimationBlendMode blendMode GetBlendMode SetBlendMode
+END
+
+CLASS GameObject : Object
+
+ // Samples an animation at a given time for any animated properties.
+ CUSTOM void SampleAnimation (AnimationClip animation, float time) { SampleAnimation (*self, *animation, time, animation->GetWrapMode()); }
+
+END
+
+
+CSRAW }
diff --git a/Runtime/Animation/ScriptBindings/AnimatorBindings.txt b/Runtime/Animation/ScriptBindings/AnimatorBindings.txt
new file mode 100644
index 0000000..d729bc7
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/AnimatorBindings.txt
@@ -0,0 +1,677 @@
+
+C++RAW
+
+#include "UnityPrefix.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Animation/Animator.h"
+#include "Runtime/Animation/RuntimeAnimatorController.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/mecanim/human/human.h"
+#include "Runtime/mecanim/generic/crc32.h"
+#include "Runtime/Animation/AnimationClip.h"
+#include "Runtime/Animation/AnimatorOverrideController.h"
+#include "Runtime/Scripting/ScriptingExportUtility.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/Scripting.h"
+
+using namespace Unity;
+
+CSRAW
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+// Target
+ENUM AvatarTarget
+ // The root, the position of the game object
+ Root = 0,
+ // The body, center of mass
+ Body = 1,
+ // The left foot
+ LeftFoot = 2,
+ // The right foot
+ RightFoot = 3,
+ // The left hand
+ LeftHand = 4,
+ // The right hand
+ RightHand = 5,
+END
+
+// IK Goal
+ENUM AvatarIKGoal
+ // The left foot
+ LeftFoot = 0,
+ // The right foot
+ RightFoot = 1,
+ // The left hand
+ LeftHand = 2,
+ // The right hand
+ RightHand = 3
+END
+
+// Information about what animation clips is played and its weight
+STRUCT AnimationInfo
+
+ // Animation clip that is played
+ CSRAW public AnimationClip clip { get {return m_ClipInstanceID != 0 ? ClipInstanceToScriptingObject(m_ClipInstanceID) : null; } }
+
+ // The weight of the animation clip
+ CSRAW public float weight { get { return m_Weight;}}
+
+ CUSTOM private static AnimationClip ClipInstanceToScriptingObject(int instanceID)
+ {
+ return Scripting::ScriptingWrapperFor(PPtr<AnimationClip>(instanceID));
+ }
+
+ CSRAW private int m_ClipInstanceID;
+ CSRAW private float m_Weight;
+END
+
+C++RAW
+
+struct MonoAnimationInfo
+{
+ int clipInstanceID;
+ float weight;
+};
+
+void AnimationInfoToMono ( const AnimationInfo& src, MonoAnimationInfo &dest)
+{
+ dest.clipInstanceID = src.clip.IsValid() ? src.clip.GetInstanceID() : 0;
+ dest.weight = src.weight;
+}
+
+static int ScriptingStringToCRC32 (ICallString& stringValue);
+
+
+// Culling mode for the Animator
+ENUM AnimatorCullingMode
+
+ // Always animate the entire character. Object is animated even when offscreen.
+ AlwaysAnimate = 0,
+
+ // Animation is disabled when renderers are not visible.
+ BasedOnRenderers = 1
+END
+
+// Information about the current or next state
+STRUCT AnimatorStateInfo
+
+ // Does /name/ match the name of the active state in the statemachine.
+ CSRAW public bool IsName (string name) { int hash = Animator.StringToHash (name); return hash == m_Name || hash == m_Path; }
+
+ // For backwards compatibility this is actually the path...
+ // In the future it would be good to come up with a new name for the name & path.
+ CSRAW public int nameHash { get { return m_Path; } }
+
+ // Normalized time of the State
+ CSRAW public float normalizedTime { get { return m_NormalizedTime; } }
+
+ // Current duration of the state
+ CSRAW public float length { get { return m_Length; } }
+
+ // The Tag of the State
+ CSRAW public int tagHash { get { return m_Tag; } }
+
+ // Does /tag/ match the tag of the active state in the statemachine.
+ CSRAW public bool IsTag (string tag) { return Animator.StringToHash (tag) == m_Tag; }
+
+ // Is the state looping
+ CSRAW public bool loop { get { return m_Loop != 0;} }
+
+ CSRAW private int m_Name;
+ CSRAW private int m_Path;
+ CSRAW private float m_NormalizedTime;
+ CSRAW private float m_Length;
+ CSRAW private int m_Tag;
+ CSRAW private int m_Loop;
+END
+
+// Information about the current transition
+STRUCT AnimatorTransitionInfo
+
+ // Does /name/ match the name of the active Transition.
+ CSRAW public bool IsName (string name) { return Animator.StringToHash (name) == m_Name ; }
+
+ // Does /userName/ match the name of the active Transition.
+ CSRAW public bool IsUserName (string name) { return Animator.StringToHash (name) == m_UserName ; }
+
+ // The unique name of the Transition
+ CSRAW public int nameHash { get { return m_Name; } }
+
+ // The user-specidied name of the Transition
+ CSRAW public int userNameHash { get { return m_UserName; } }
+
+ // Normalized time of the Transition
+ CSRAW public float normalizedTime { get { return m_NormalizedTime; } }
+
+
+ CSRAW private int m_Name;
+ CSRAW private int m_UserName;
+ CSRAW private float m_NormalizedTime;
+
+END
+
+
+
+// To specify position and rotation weight mask for Animator::MatchTarget
+STRUCT MatchTargetWeightMask
+
+ // MatchTargetWeightMask contructor
+ CSRAW public MatchTargetWeightMask(Vector3 positionXYZWeight, float rotationWeight)
+ {
+ m_PositionXYZWeight = positionXYZWeight;
+ m_RotationWeight = rotationWeight;
+ }
+
+ // Position XYZ weight
+ CSRAW public Vector3 positionXYZWeight
+ {
+ get { return m_PositionXYZWeight;}
+ set { m_PositionXYZWeight = value; }
+ }
+
+ // Rotation weight
+ CSRAW public float rotationWeight
+ {
+ get { return m_RotationWeight;}
+ set { m_RotationWeight =value;}
+ }
+
+ CSRAW private Vector3 m_PositionXYZWeight;
+ CSRAW private float m_RotationWeight;
+END
+
+C++RAW
+ enum HumanBodyBones { Hips=0, LeftUpperLeg, RightUpperLeg, LeftLowerLeg, RightLowerLeg, LeftFoot, RightFoot, Spine, Chest, Neck, Head, LeftShoulder, RightShoulder, LeftUpperArm, RightUpperArm, LeftLowerArm, RightLowerArm, LeftHand, RightHand, LeftToes, RightToes, LeftEye, RightEye, Jaw, LastBone};
+
+
+// Interface to control the Mecanim animation system
+CLASS Animator : Behaviour
+
+ // Returns true if the current rig is optimizable
+ CUSTOM_PROP bool isOptimizable { return self->IsOptimizable(); }
+
+ // Returns true if the current rig is ''humanoid'', false if it is ''generic''
+ CUSTOM_PROP bool isHuman { return self->IsHuman(); }
+
+ // Returns true if the current generic rig has a root motion
+ CUSTOM_PROP bool hasRootMotion { return self->HasRootMotion(); }
+
+ // Returns the scale of the current Avatar for a humanoid rig, (1 by default if the rig is generic)
+ CUSTOM_PROP float humanScale { return self->GetHumanScale(); }
+
+ // Gets the value of a float parameter
+ CSRAW public float GetFloat(string name) { return GetFloatString(name); }
+ // Gets the value of a float parameter
+ CSRAW public float GetFloat(int id) { return GetFloatID(id); }
+ // Sets the value of a float parameter
+ CSRAW public void SetFloat(string name, float value) { SetFloatString(name, value);}
+ // Sets the value of a float parameter
+ CSRAW public void SetFloat(string name, float value, float dampTime, float deltaTime) { SetFloatStringDamp(name, value, dampTime, deltaTime);}
+
+ // Sets the value of a float parameter
+ CSRAW public void SetFloat(int id, float value) { SetFloatID(id, value);}
+ // Sets the value of a float parameter
+ CSRAW public void SetFloat(int id, float value, float dampTime, float deltaTime) { SetFloatIDDamp(id, value, dampTime, deltaTime); }
+
+ // Gets the value of a bool parameter
+ CSRAW public bool GetBool(string name) { return GetBoolString(name);}
+ // Gets the value of a bool parameter
+ CSRAW public bool GetBool(int id) { return GetBoolID(id);}
+ // Sets the value of a bool parameter
+ CSRAW public void SetBool(string name, bool value) { SetBoolString(name, value);}
+ // Sets the value of a bool parameter
+ CSRAW public void SetBool(int id, bool value) { SetBoolID(id, value);}
+
+ // Gets the value of an integer parameter
+ CSRAW public int GetInteger(string name) { return GetIntegerString(name);}
+ // Gets the value of an integer parameter
+ CSRAW public int GetInteger(int id) { return GetIntegerID(id);}
+ // Sets the value of an integer parameter
+ CSRAW public void SetInteger(string name, int value) { SetIntegerString(name, value);}
+
+ // Sets the value of an integer parameter
+ CSRAW public void SetInteger(int id, int value) { SetIntegerID(id, value); }
+
+ // Sets the trigger parameter on
+ CSRAW public void SetTrigger(string name) { SetTriggerString(name); }
+
+ // Sets the trigger parameter at on
+ CSRAW public void SetTrigger(int id) { SetTriggerID(id); }
+
+ // Resets the trigger parameter at off
+ CSRAW public void ResetTrigger(string name) { ResetTriggerString(name); }
+
+ // Resets the trigger parameter at off
+ CSRAW public void ResetTrigger(int id) { ResetTriggerID(id); }
+
+ // Returns true if a parameter is controlled by an additional curve on an animation
+ CSRAW public bool IsParameterControlledByCurve(string name) { return IsParameterControlledByCurveString(name); }
+ // Returns true if a parameter is controlled by an additional curve on an animation
+ CSRAW public bool IsParameterControlledByCurve(int id) { return IsParameterControlledByCurveID(id); }
+
+ // Gets the avatar delta position for the last evaluated frame
+ CUSTOM_PROP Vector3 deltaPosition { return self->GetDeltaPosition(); }
+ // Gets the avatar delta rotation for the last evaluated frame
+ CUSTOM_PROP Quaternion deltaRotation { return self->GetDeltaRotation(); }
+
+ // The root position, the position of the game object
+ CUSTOM_PROP Vector3 rootPosition { return self->GetAvatarPosition();} { self->SetAvatarPosition(value);}
+ // The root rotation, the rotation of the game object
+ CUSTOM_PROP Quaternion rootRotation { return self->GetAvatarRotation();} { self->SetAvatarRotation(value);}
+
+ // Root is controlled by animations
+ CUSTOM_PROP bool applyRootMotion { return self->GetApplyRootMotion(); } { self->SetApplyRootMotion(value); }
+
+ // When turned on, animations will be executed in the physics loop. This is only useful in conjunction with kinematic rigidbodies.
+ CUSTOM_PROP bool animatePhysics { return self->GetAnimatePhysics(); } { self->SetAnimatePhysics(value); }
+
+ // Tell if the corresponding Character has transform hierarchy.
+ CUSTOM_PROP bool hasTransformHierarchy { return self->GetHasTransformHierarchy(); }
+
+ // The current gravity weight based on current animations that are played
+ CUSTOM_PROP float gravityWeight { return self->GetGravityWeight(); }
+
+ // The position of the body center of mass
+ CUSTOM_PROP Vector3 bodyPosition { return self->GetBodyPosition(); } { self->SetBodyPosition(value); }
+ // The rotation of the body center of mass
+ CUSTOM_PROP Quaternion bodyRotation { return self->GetBodyRotation(); } { self->SetBodyRotation(value); }
+
+ // Gets the position of an IK goal
+ CUSTOM Vector3 GetIKPosition( AvatarIKGoal goal) { return self->GetGoalPosition(goal) ;}
+ // Sets the position of an IK goal
+ CUSTOM void SetIKPosition( AvatarIKGoal goal, Vector3 goalPosition) { self->SetGoalPosition(goal,goalPosition); }
+
+ // Gets the rotation of an IK goal
+ CUSTOM Quaternion GetIKRotation( AvatarIKGoal goal) { return self->GetGoalRotation(goal); }
+ // Sets the rotation of an IK goal
+ CUSTOM void SetIKRotation( AvatarIKGoal goal, Quaternion goalRotation) { self->SetGoalRotation(goal, goalRotation); }
+
+ // Gets the translative weight of an IK goal (0 = at the original animation before IK, 1 = at the goal)
+ CUSTOM float GetIKPositionWeight ( AvatarIKGoal goal) { return self->GetGoalWeightPosition(goal); }
+ // Sets the translative weight of an IK goal (0 = at the original animation before IK, 1 = at the goal)
+ CUSTOM void SetIKPositionWeight ( AvatarIKGoal goal, float value) { self->SetGoalWeightPosition(goal,value); }
+
+ // Gets the rotational weight of an IK goal (0 = rotation before IK, 1 = rotation at the IK goal)
+ CUSTOM float GetIKRotationWeight( AvatarIKGoal goal) { return self->GetGoalWeightRotation(goal); }
+ // Sets the rotational weight of an IK goal (0 = rotation before IK, 1 = rotation at the IK goal)
+ CUSTOM void SetIKRotationWeight( AvatarIKGoal goal, float value) { self->SetGoalWeightRotation(goal,value); }
+
+ // Sets the look at position
+ CUSTOM void SetLookAtPosition(Vector3 lookAtPosition) { self->SetLookAtPosition(lookAtPosition); }
+
+ //Set look at weights
+ CUSTOM void SetLookAtWeight(float weight, float bodyWeight = 0.00f, float headWeight = 1.00f, float eyesWeight = 0.00f, float clampWeight = 0.50f)
+ {
+ self->SetLookAtBodyWeight(weight*bodyWeight);
+ self->SetLookAtHeadWeight(weight*headWeight);
+ self->SetLookAtEyesWeight(weight*eyesWeight);
+ self->SetLookAtClampWeight(clampWeight);
+ }
+
+ // Automatic stabilization of feet during transition and blending
+ CUSTOM_PROP bool stabilizeFeet { return self->GetStabilizeFeet(); } { self->SetStabilizeFeet(value);}
+
+ // The AnimatorController layer count
+ CUSTOM_PROP int layerCount { return self->GetLayerCount(); }
+ // Gets name of the layer
+ CUSTOM string GetLayerName( int layerIndex) { return scripting_string_new(self->GetLayerName(layerIndex)) ; }
+ // Gets the layer's current weight
+ CUSTOM float GetLayerWeight( int layerIndex) { return self->GetLayerWeight(layerIndex) ; }
+ // Sets the layer's current weight
+ CUSTOM void SetLayerWeight( int layerIndex, float weight) { self->SetLayerWeight(layerIndex, weight); }
+
+
+ // Gets the current State information on a specified AnimatorController layer
+ CUSTOM AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex)
+ {
+ AnimatorStateInfo info;
+ self->GetAnimatorStateInfo (layerIndex, true, info);
+ return info;
+ }
+
+ // Gets the next State information on a specified AnimatorController layer
+ CUSTOM AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex)
+ {
+ AnimatorStateInfo info;
+ self->GetAnimatorStateInfo(layerIndex, false, info);
+ return info;
+ }
+
+ // Gets the Transition information on a specified AnimatorController layer
+ CUSTOM AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex)
+ {
+ AnimatorTransitionInfo info;
+ self->GetAnimatorTransitionInfo(layerIndex, info);
+ return info;
+ }
+
+ CONDITIONAL !UNITY_FLASH
+ // Gets the list of AnimationInfo currently played by the current state
+ CUSTOM AnimationInfo[] GetCurrentAnimationClipState (int layerIndex)
+ {
+ dynamic_array<AnimationInfo> clips (kMemTempAlloc);
+ self->GetAnimationClipState(layerIndex, true, clips);
+ return DynamicArrayToScriptingStructArray(clips, GetMonoManager().GetCommonClasses().animationInfo, AnimationInfoToMono);
+ }
+
+ CONDITIONAL !UNITY_FLASH
+ // Gets the list of AnimationInfo currently played by the next state
+ CUSTOM AnimationInfo[] GetNextAnimationClipState (int layerIndex)
+ {
+ dynamic_array<AnimationInfo> clips (kMemTempAlloc);
+ self->GetAnimationClipState(layerIndex, false, clips);
+ return DynamicArrayToScriptingStructArray(clips, GetMonoManager().GetCommonClasses().animationInfo, AnimationInfoToMono);
+ }
+
+ // Is the specified AnimatorController layer in a transition
+ CUSTOM bool IsInTransition(int layerIndex) { return self->IsInTransition(layerIndex); }
+
+
+ // Blends pivot point between body center of mass and feet pivot. At 0%, the blending point is body center of mass. At 100%, the blending point is feet pivot
+ CUSTOM_PROP float feetPivotActive { return self->GetFeetPivotActive(); } { self->SetFeetPivotActive(value);}
+ // Gets the pivot weight
+ CUSTOM_PROP float pivotWeight { return self->GetPivotWeight(); }
+ // Get the current position of the pivot
+ CUSTOM_PROP Vector3 pivotPosition { return self->GetPivotPosition(); }
+
+
+ // Automatically adjust the gameobject position and rotation so that the AvatarTarget reaches the matchPosition when the current state is at the specified progress
+ CUSTOM void MatchTarget(Vector3 matchPosition, Quaternion matchRotation, AvatarTarget targetBodyPart, MatchTargetWeightMask weightMask, float startNormalizedTime, float targetNormalizedTime = 1)
+ {
+ self->MatchTarget(matchPosition, matchRotation, (int)targetBodyPart, weightMask, startNormalizedTime, targetNormalizedTime);
+ }
+
+ // Interrupts the automatic target matching
+ CUSTOM void InterruptMatchTarget(bool completeMatch = true) { self->InterruptMatchTarget(completeMatch);}
+ // If automatic matching is active
+ CUSTOM_PROP bool isMatchingTarget{ return self->IsMatchingTarget();}
+
+
+ // The playback speed of the Animator. 1 is normal playback speed
+ CUSTOM_PROP float speed { return self->GetSpeed() ; } { self->SetSpeed(value);}
+
+ // Force the normalized time of a state to a user defined value
+ OBSOLETE warning ForceStateNormalizedTime is deprecated. Please use Play or CrossFade instead.
+ CSRAW public void ForceStateNormalizedTime(float normalizedTime) { Play(0, 0, normalizedTime); }
+
+
+ CSRAW public void CrossFade (string stateName, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity)
+ {
+ CrossFade (StringToHash(stateName), transitionDuration, layer, normalizedTime);
+ }
+ CUSTOM void CrossFade (int stateNameHash, float transitionDuration, int layer = -1, float normalizedTime = float.NegativeInfinity)
+ {
+ self->GotoState(layer, stateNameHash, normalizedTime, transitionDuration);
+ }
+
+ CSRAW public void Play (string stateName, int layer = -1, float normalizedTime = float.NegativeInfinity)
+ {
+ Play (StringToHash(stateName), layer, normalizedTime);
+ }
+ CUSTOM void Play (int stateNameHash, int layer = -1, float normalizedTime = float.NegativeInfinity)
+ {
+ self->GotoState(layer, stateNameHash, normalizedTime, 0.0F);
+ }
+
+
+
+ // Sets an AvatarTarget and a targetNormalizedTime for the current state
+ CUSTOM void SetTarget(AvatarTarget targetIndex, float targetNormalizedTime) {self->SetTarget((int)targetIndex, targetNormalizedTime);}
+ // Returns the position of the target specified by SetTarget(AvatarTarget targetIndex, float targetNormalizedTime))
+ CUSTOM_PROP Vector3 targetPosition { return self->GetTargetPosition(); }
+ // Returns the rotation of the target specified by SetTarget(AvatarTarget targetIndex, float targetNormalizedTime))
+ CUSTOM_PROP Quaternion targetRotation { return self->GetTargetRotation(); }
+
+
+ OBSOLETE error use mask and layers to control subset of transfroms in a skeleton
+ CUSTOM bool IsControlled(Transform transform) {return false;}
+
+ // Returns ture if a transform a bone controlled by human
+ CUSTOM internal bool IsBoneTransform(Transform transform) {return self->IsBoneTransform(transform);}
+
+ CUSTOM_PROP internal Transform avatarRoot {return Scripting::ScriptingWrapperFor(self->GetAvatarRoot());}
+
+ // Returns transform mapped to this human bone id
+ CUSTOM Transform GetBoneTransform(HumanBodyBones humanBoneId) {return Scripting::ScriptingWrapperFor(self->GetBoneTransform((int)humanBoneId));}
+
+ // Controls culling of this Animator component.
+ AUTO_PROP AnimatorCullingMode cullingMode GetCullingMode SetCullingMode
+
+ // Sets the animator in playback mode
+ CUSTOM void StartPlayback() { self->StartPlayback();}
+
+ // Stops animator playback mode
+ CUSTOM void StopPlayback() { self->StopPlayback();}
+
+ // Plays recorded data
+ CUSTOM_PROP float playbackTime {return self->GetPlaybackTime(); } { self->SetPlaybackTime(value);}
+
+ // Sets the animator in record mode
+ CUSTOM void StartRecording(int frameCount) { self->StartRecording(frameCount);}
+
+ // Stops animator record mode
+ CUSTOM void StopRecording() { self->StopRecording();}
+
+ // The time at which the recording data starts
+ CUSTOM_PROP float recorderStartTime { return self->GetRecorderStartTime();} {}
+
+ // The time at which the recoding data stops
+ CUSTOM_PROP float recorderStopTime { return self->GetRecorderStopTime();} {}
+
+ // The runtime representation of AnimatorController that controls the Animator
+ CUSTOM_PROP RuntimeAnimatorController runtimeAnimatorController {return Scripting::ScriptingWrapperFor(self->GetRuntimeAnimatorController());} { self->SetRuntimeAnimatorController(value);}
+
+ C++RAW static int ScriptingStringToCRC32 (ICallString& stringValue)
+ {
+ if(stringValue.IsNull()) return 0;
+ #if ENABLE_MONO
+ const gunichar2* chars = mono_string_chars(stringValue.str);
+ const size_t length = stringValue.Length();
+
+ if (IsUtf16InAsciiRange (chars, length))
+ {
+ return mecanim::processCRC32UTF16Ascii (chars, length);
+ }
+ else
+ #endif
+ {
+ return mecanim::processCRC32 (stringValue.AsUTF8().c_str());
+ }
+ }
+
+ // Generates an parameter id from a string
+ THREAD_SAFE
+ CUSTOM static int StringToHash (string name) { return ScriptingStringToCRC32(name); }
+
+ // Gets/Sets the current Avatar
+ CUSTOM_PROP Avatar avatar {return Scripting::ScriptingWrapperFor(self->GetAvatar());} { self->SetAvatar(value);}
+
+C++RAW
+
+ #define GET_NAME_IMPL(Func,x) \
+ x value; \
+ GetSetValueResult result = self->Func(ScriptingStringToCRC32(name), value); \
+ if (result != kGetSetSuccess) \
+ self->ValidateParameterString (result, name); \
+ return value;
+
+ #define SET_NAME_IMPL(Func) \
+ GetSetValueResult result = self->Func(ScriptingStringToCRC32(name), value); \
+ if (result != kGetSetSuccess) \
+ self->ValidateParameterString (result, name);
+
+
+ #define GET_ID_IMPL(Func,x) \
+ x value; \
+ GetSetValueResult result = self->Func(id, value); \
+ if (result != kGetSetSuccess) \
+ self->ValidateParameterID (result, id); \
+ return value;
+
+ #define SET_ID_IMPL(Func) \
+ GetSetValueResult result = self->Func(id, value); \
+ if (result != kGetSetSuccess) \
+ self->ValidateParameterID (result, id);
+
+
+ // Internal
+ CUSTOM private void SetFloatString(string name, float value) { SET_NAME_IMPL(SetFloat) }
+ CUSTOM private void SetFloatID(int id, float value) { SET_ID_IMPL (SetFloat) }
+
+ CUSTOM private float GetFloatString(string name) { GET_NAME_IMPL(GetFloat, float) }
+ CUSTOM private float GetFloatID(int id) { GET_ID_IMPL (GetFloat, float) }
+
+ CUSTOM private void SetBoolString(string name, bool value) { SET_NAME_IMPL(SetBool) }
+ CUSTOM private void SetBoolID(int id, bool value) { SET_ID_IMPL (SetBool) }
+
+ CUSTOM private bool GetBoolString(string name) { GET_NAME_IMPL(GetBool, bool) }
+ CUSTOM private bool GetBoolID(int id) { GET_ID_IMPL (GetBool, bool) }
+
+
+ CUSTOM private void SetIntegerString(string name, int value) { SET_NAME_IMPL(SetInteger) }
+ CUSTOM private void SetIntegerID(int id, int value) { SET_ID_IMPL (SetInteger)}
+
+ CUSTOM private int GetIntegerString(string name) { GET_NAME_IMPL(GetInteger, int) }
+ CUSTOM private int GetIntegerID(int id) { GET_ID_IMPL (GetInteger, int) }
+
+ CUSTOM private void SetTriggerString(string name)
+ {
+ GetSetValueResult result = self->SetTrigger(ScriptingStringToCRC32(name));
+ if (result != kGetSetSuccess)
+ self->ValidateParameterString (result, name);
+ }
+
+ CUSTOM private void SetTriggerID(int id)
+ {
+ GetSetValueResult result = self->SetTrigger(id);
+ if (result != kGetSetSuccess)
+ self->ValidateParameterID (result, id);
+ }
+
+ CUSTOM private void ResetTriggerString(string name)
+ {
+ GetSetValueResult result = self->ResetTrigger(ScriptingStringToCRC32(name));
+ if (result != kGetSetSuccess)
+ self->ValidateParameterString (result, name);
+ }
+
+ CUSTOM private void ResetTriggerID(int id)
+ {
+ GetSetValueResult result = self->ResetTrigger(id);
+ if (result != kGetSetSuccess)
+ self->ValidateParameterID (result, id);
+ }
+
+ CUSTOM private bool IsParameterControlledByCurveString(string name)
+ {
+ GetSetValueResult result = self->ParameterControlledByCurve(ScriptingStringToCRC32(name));
+ if (result == kParameterIsControlledByCurve)
+ return true;
+ else if (result == kGetSetSuccess)
+ return false;
+ else
+ {
+ self->ValidateParameterString (result, name);
+ return false;
+ }
+ }
+
+ CUSTOM private bool IsParameterControlledByCurveID(int id)
+ {
+ GetSetValueResult result = self->ParameterControlledByCurve(id);
+ if (result == kParameterIsControlledByCurve)
+ return true;
+ else if (result == kGetSetSuccess)
+ return false;
+ else
+ {
+ self->ValidateParameterID (result, id);
+ return false;
+ }
+ }
+
+ CUSTOM private void SetFloatStringDamp(string name, float value, float dampTime, float deltaTime)
+ {
+ GetSetValueResult result = self->SetFloatDamp(ScriptingStringToCRC32(name), value, dampTime, deltaTime);
+ if (result != kGetSetSuccess)
+ self->ValidateParameterString (result, name);
+ }
+
+ CUSTOM private void SetFloatIDDamp(int id, float value, float dampTime, float deltaTime)
+ {
+ GetSetValueResult result = self->SetFloatDamp(id, value, dampTime, deltaTime);
+ if (result != kGetSetSuccess)
+ self->ValidateParameterID (result, id);
+ }
+
+ // True if additional layers affect the center of mass
+ CUSTOM_PROP bool layersAffectMassCenter {return self->GetLayersAffectMassCenter();} { self->SetLayersAffectMassCenter(value);}
+
+
+ // Get left foot bottom height.
+ CUSTOM_PROP float leftFeetBottomHeight {return self->GetLeftFeetBottomHeight();}
+
+ // Get right foot bottom height.
+ CUSTOM_PROP float rightFeetBottomHeight {return self->GetRightFeetBottomHeight();}
+
+ CONDITIONAL UNITY_EDITOR
+ CUSTOM_PROP internal bool supportsOnAnimatorMove { return self->SupportsOnAnimatorMove (); }
+
+ CONDITIONAL UNITY_EDITOR
+ CUSTOM internal void WriteDefaultPose() { self->WriteDefaultPose(); }
+
+ CUSTOM void Update(float deltaTime) { self->Update(deltaTime);}
+
+ CONDITIONAL UNITY_EDITOR
+ // Evalutes only the StateMachine, does not write into transforms, uses previous deltaTime
+ // Mostly used for editor previews ( BlendTrees )
+ CUSTOM internal void EvaluateSM() {self->EvaluateSM();}
+
+ CONDITIONAL UNITY_EDITOR
+ CUSTOM internal string GetCurrentStateName(int layerIndex) { return scripting_string_new(self->GetAnimatorStateName(layerIndex,true));}
+
+ CONDITIONAL UNITY_EDITOR
+ CUSTOM internal string GetNextStateName(int layerIndex) { return scripting_string_new(self->GetAnimatorStateName(layerIndex,false));}
+
+ CUSTOM_PROP private bool isInManagerList { return self->IsInManagerList();}
+
+ AUTO_PROP bool logWarnings GetLogWarnings SetLogWarnings
+ AUTO_PROP bool fireEvents GetFireEvents SetFireEvents
+
+
+
+
+ OBSOLETE warning GetVector is deprecated.
+ CSRAW public Vector3 GetVector(string name) { return Vector3.zero; }
+ OBSOLETE warning GetVector is deprecated.
+ CSRAW public Vector3 GetVector(int id) { return Vector3.zero; }
+ OBSOLETE warning SetVector is deprecated.
+ CSRAW public void SetVector(string name, Vector3 value) { }
+ OBSOLETE warning SetVector is deprecated.
+ CSRAW public void SetVector(int id, Vector3 value) { }
+
+ OBSOLETE warning GetQuaternion is deprecated.
+ CSRAW public Quaternion GetQuaternion(string name) { return Quaternion.identity;}
+ OBSOLETE warning GetQuaternion is deprecated.
+ CSRAW public Quaternion GetQuaternion(int id) { return Quaternion.identity; }
+ OBSOLETE warning SetQuaternion is deprecated.
+ CSRAW public void SetQuaternion(string name, Quaternion value) { }
+ OBSOLETE warning SetQuaternion is deprecated.
+ CSRAW public void SetQuaternion(int id, Quaternion value) { }
+
+END
+
+
+
+
+CSRAW }
diff --git a/Runtime/Animation/ScriptBindings/AnimatorOverrideControllerBindings.txt b/Runtime/Animation/ScriptBindings/AnimatorOverrideControllerBindings.txt
new file mode 100644
index 0000000..9f73f79
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/AnimatorOverrideControllerBindings.txt
@@ -0,0 +1,138 @@
+C++RAW
+
+#include "UnityPrefix.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Animation/AnimatorOverrideController.h"
+#include "Runtime/Animation/AnimationClip.h"
+#include "Runtime/Animation/RuntimeAnimatorController.h"
+#include "Runtime/Scripting/ScriptingExportUtility.h"
+
+using namespace Unity;
+
+CSRAW
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+CSRAW [System.Serializable]
+CSRAW [StructLayout (LayoutKind.Sequential)]
+CLASS AnimationClipPair
+ public AnimationClip originalClip;
+ public AnimationClip overrideClip;
+END
+
+// AnimatorOverrideController definition
+CLASS AnimatorOverrideController : RuntimeAnimatorController
+
+ // Creates a new animation clip
+ CSRAW public AnimatorOverrideController()
+ {
+ Internal_CreateAnimationSet(this);
+ }
+
+ CUSTOM private static void Internal_CreateAnimationSet ([Writable]AnimatorOverrideController self)
+ {
+ Object* animationSet = NEW_OBJECT(AnimatorOverrideController);
+ animationSet->Reset();
+ Scripting::ConnectScriptingWrapperToObject (self.GetScriptingObject(), animationSet);
+ animationSet->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
+ }
+
+ // The runtime representation of AnimatorController that controls the Animator
+ CUSTOM_PROP RuntimeAnimatorController runtimeAnimatorController {return Scripting::ScriptingWrapperFor(self->GetAnimatorController());} { self->SetAnimatorController(value);}
+
+ // Returns the animation clip named /name/.
+ CSRAW public AnimationClip this [string name]
+ {
+ get { return Internal_GetClipByName(name, true); }
+ set { Internal_SetClipByName(name, value); }
+ }
+
+ CUSTOM private AnimationClip Internal_GetClipByName(string name, bool returnEffectiveClip)
+ {
+ return Scripting::ScriptingWrapperFor(self->GetClip( name, returnEffectiveClip ));
+ }
+
+ CUSTOM private void Internal_SetClipByName(string name, AnimationClip clip)
+ {
+ return self->SetClip( name, clip );
+ }
+
+ // Returns the animation clip named /name/.
+ CSRAW public AnimationClip this [AnimationClip clip]
+ {
+ get { return Internal_GetClip(clip, true); }
+ set { Internal_SetClip(clip, value); }
+ }
+
+ CUSTOM private AnimationClip Internal_GetClip(AnimationClip originalClip, bool returnEffectiveClip)
+ {
+ return Scripting::ScriptingWrapperFor(self->GetClip( originalClip, returnEffectiveClip));
+ }
+
+ CUSTOM private void Internal_SetClip(AnimationClip originalClip, AnimationClip overrideClip)
+ {
+ return self->SetClip( originalClip, overrideClip );
+ }
+
+ CONDITIONAL UNITY_EDITOR
+ CSRAW internal delegate void OnOverrideControllerDirtyCallback();
+
+ CONDITIONAL UNITY_EDITOR
+ CSRAW internal OnOverrideControllerDirtyCallback OnOverrideControllerDirty;
+
+ CONDITIONAL UNITY_EDITOR
+ CSRAW internal static void OnInvalidateOverrideController(AnimatorOverrideController controller)
+ {
+ if(controller.OnOverrideControllerDirty != null)
+ controller.OnOverrideControllerDirty();
+ }
+
+ CSRAW public AnimationClipPair[] clips
+ {
+ get
+ {
+ AnimationClip[] originalAnimationClips = GetOriginalClips();
+
+ AnimationClipPair[] clipPair = new AnimationClipPair[originalAnimationClips.Length];
+ for(int i=0;i<originalAnimationClips.Length;i++)
+ {
+ clipPair[i] = new AnimationClipPair();
+ clipPair[i].originalClip = originalAnimationClips[i];
+ clipPair[i].overrideClip = Internal_GetClip(originalAnimationClips[i], false);
+ }
+
+ return clipPair;
+ }
+ set
+ {
+ for(int i=0;i<value.Length;i++)
+ Internal_SetClip(value[i].originalClip, value[i].overrideClip);
+ }
+ }
+
+ CUSTOM private AnimationClip[] GetOriginalClips()
+ {
+ return CreateScriptingArrayFromUnityObjects(self->GetOriginalClips(), ClassID(AnimationClip));
+ }
+
+ CUSTOM private AnimationClip[] GetOverrideClips()
+ {
+ return CreateScriptingArrayFromUnityObjects(self->GetOverrideClips(), ClassID(AnimationClip));
+ }
+
+ CONDITIONAL UNITY_EDITOR
+ CUSTOM public void PerformOverrideClipListCleanup()
+ {
+ return self->PerformOverrideClipListCleanup();
+ }
+END
+
+CSRAW }
diff --git a/Runtime/Animation/ScriptBindings/Avatar.txt b/Runtime/Animation/ScriptBindings/Avatar.txt
new file mode 100644
index 0000000..52b0e93
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/Avatar.txt
@@ -0,0 +1,340 @@
+C++RAW
+
+#include "UnityPrefix.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/mecanim/human/human.h"
+
+using namespace Unity;
+
+CSRAW
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+ENUM internal BodyDoF
+
+ SpineFrontBack = 0,
+ SpineLeftRight,
+ SpineRollLeftRight,
+ ChestFrontBack,
+ ChestLeftRight,
+ ChestRollLeftRight,
+ LastBodyDoF
+END
+
+ENUM internal HeadDoF
+ NeckFrontBack = 0,
+ NeckLeftRight,
+ NeckRollLeftRight,
+ HeadFrontBack,
+ HeadLeftRight,
+ HeadRollLeftRight,
+ LeftEyeDownUp,
+ LeftEyeInOut,
+ RightEyeDownUp,
+ RightEyeInOut,
+ JawDownUp,
+ JawLeftRight,
+ LastHeadDoF
+END
+
+ENUM internal LegDoF
+ UpperLegFrontBack = 0,
+ UpperLegInOut,
+ UpperLegRollInOut,
+ LegCloseOpen,
+ LegRollInOut,
+ FootCloseOpen,
+ FootInOut,
+ ToesUpDown,
+ LastLegDoF
+END
+
+ENUM internal ArmDoF
+ ShoulderDownUp = 0,
+ ShoulderFrontBack,
+ ArmDownUp,
+ ArmFrontBack,
+ ArmRollInOut,
+ ForeArmCloseOpen,
+ ForeArmRollInOut,
+ HandDownUp,
+ HandInOut,
+ LastArmDoF
+END
+
+ENUM internal FingerDoF
+ ProximalDownUp = 0,
+ ProximalInOut,
+ IntermediateCloseOpen,
+ DistalCloseOpen,
+ LastFingerDoF
+END
+
+ENUM internal DoF
+ BodyDoFStart = 0,
+ HeadDoFStart = (int)BodyDoFStart + (int)BodyDoF.LastBodyDoF,
+ LeftLegDoFStart = (int)HeadDoFStart + (int)HeadDoF.LastHeadDoF,
+ RightLegDoFStart = (int)LeftLegDoFStart + (int)LegDoF.LastLegDoF,
+ LeftArmDoFStart = (int)RightLegDoFStart + (int)LegDoF.LastLegDoF,
+ RightArmDoFStart = (int)LeftArmDoFStart + (int)ArmDoF.LastArmDoF,
+
+ LeftThumbDoFStart = (int)RightArmDoFStart + (int)ArmDoF.LastArmDoF,
+ LeftIndexDoFStart = (int)LeftThumbDoFStart + (int)FingerDoF.LastFingerDoF,
+ LeftMiddleDoFStart = (int)LeftIndexDoFStart + (int)FingerDoF.LastFingerDoF,
+
+ LeftRingDoFStart = (int)LeftMiddleDoFStart + (int)FingerDoF.LastFingerDoF,
+ LeftLittleDoFStart = (int)LeftRingDoFStart + (int)FingerDoF.LastFingerDoF,
+
+ RightThumbDoFStart = (int)LeftLittleDoFStart + (int)FingerDoF.LastFingerDoF,
+ RightIndexDoFStart = (int)RightThumbDoFStart + (int)FingerDoF.LastFingerDoF,
+ RightMiddleDoFStart = (int)RightIndexDoFStart + (int)FingerDoF.LastFingerDoF,
+ RightRingDoFStart = (int)RightMiddleDoFStart + (int)FingerDoF.LastFingerDoF,
+ RightLittleDoFStart = (int)RightRingDoFStart + (int)FingerDoF.LastFingerDoF,
+
+ LastDoF = (int)RightLittleDoFStart + (int)FingerDoF.LastFingerDoF
+END
+
+// Human Body Bones
+ENUM HumanBodyBones
+ // This is the Hips bone
+ Hips = 0,
+
+ // This is the Left Upper Leg bone
+ LeftUpperLeg = 1,
+
+ // This is the Right Upper Leg bone
+ RightUpperLeg = 2,
+
+ // This is the Left Knee bone
+ LeftLowerLeg = 3,
+
+ // This is the Right Knee bone
+ RightLowerLeg = 4,
+
+ // This is the Left Ankle bone
+ LeftFoot = 5,
+
+ // This is the Right Ankle bone
+ RightFoot = 6,
+
+ // This is the first Spine bone
+ Spine = 7,
+
+ // This is the Chest bone
+ Chest = 8,
+
+ // This is the Neck bone
+ Neck = 9,
+
+ // This is the Head bone
+ Head = 10,
+
+ // This is the Left Shoulder bone
+ LeftShoulder = 11,
+
+ // This is the Right Shoulder bone
+ RightShoulder = 12,
+
+ // This is the Left Upper Arm bone
+ LeftUpperArm = 13,
+
+ // This is the Right Upper Arm bone
+ RightUpperArm = 14,
+
+ // This is the Left Elbow bone
+ LeftLowerArm = 15,
+
+ // This is the Right Elbow bone
+ RightLowerArm = 16,
+
+ // This is the Left Wrist bone
+ LeftHand = 17,
+
+ // This is the Right Wrist bone
+ RightHand = 18,
+
+ // This is the Left Toes bone
+ LeftToes = 19,
+
+ // This is the Right Toes bone
+ RightToes = 20,
+
+ // This is the Left Eye bone
+ LeftEye = 21,
+
+ // This is the Right Eye bone
+ RightEye = 22,
+
+ // This is the Jaw bone
+ Jaw = 23,
+
+ // This is the Last bone index delimiter
+ LastBone = 24
+END
+
+ENUM internal HumanFingerBones
+ ThumbProximal = 0,
+ ThumbIntermediate,
+ ThumbDistal,
+
+ IndexProximal,
+ IndexIntermediate,
+ IndexDistal,
+
+ MiddleProximal,
+ MiddleIntermediate,
+ MiddleDistal,
+
+ RingProximal,
+ RingIntermediate,
+ RingDistal,
+
+ LittleProximal,
+ LittleIntermediate,
+ LittleDistal,
+ LastBone
+END
+
+ENUM internal HumanBodyPart
+ BodyStart = 0,
+ LeftFingerStart = (int)BodyStart + (int)HumanBodyBones.LastBone,
+ RightFingerStart = (int)LeftFingerStart + (int)HumanFingerBones.LastBone
+END
+
+ENUM internal HumanParameter
+ UpperArmTwist = 0,
+ LowerArmTwist,
+ UpperLegTwist,
+ LowerLegTwist,
+ ArmStretch,
+ LegStretch,
+ FeetSpacing
+END
+
+
+// Avatar definition
+CLASS Avatar : Object
+ // Return true if this avatar is a valid mecanim avatar. It can be a generic avatar or a human avatar.
+ CUSTOM_PROP bool isValid {
+ return self->IsValid();
+ }
+
+ // Return true if this avatar is a valid human avatar.
+ CUSTOM_PROP bool isHuman
+ {
+ return self->IsValid() && self->GetAsset()->isHuman();
+ }
+
+ CUSTOM internal void SetMuscleMinMax(int muscleId, float min, float max)
+ {
+ self->SetMuscleMinMax(muscleId, min, max);
+ }
+
+ CUSTOM internal void SetParameter(int parameterId, float value)
+ {
+ self->SetParameter(parameterId, value);
+ }
+
+ CUSTOM internal float GetAxisLength(int humanId)
+ {
+ return self->GetAxisLength(humanId);
+ }
+
+ CUSTOM internal Quaternion GetPreRotation(int humanId)
+ {
+ return self->GetPreRotation(humanId);
+ }
+
+ CUSTOM internal Quaternion GetPostRotation(int humanId)
+ {
+ return self->GetPostRotation(humanId);
+ }
+
+ CUSTOM internal Quaternion GetZYPostQ(int humanId, Quaternion parentQ, Quaternion q) {
+ return self->GetZYPostQ(humanId, parentQ, q);
+ }
+
+ CUSTOM internal Quaternion GetZYRoll(int humanId, Vector3 uvw) {
+ return self->GetZYRoll(humanId, uvw);
+ }
+
+ CUSTOM internal Vector3 GetLimitSign(int humanId){
+ return self->GetLimitSign(humanId);
+ }
+
+END
+
+// Humanoid definition
+CLASS HumanTrait : Object
+
+ // Number of muscles
+ CUSTOM_PROP static int MuscleCount
+ {
+ return HumanTrait::MuscleCount;
+ }
+
+ // Muscle's name
+ CUSTOM_PROP static string[] MuscleName
+ {
+ return Scripting::StringVectorToMono( HumanTrait::GetMuscleName() );
+ }
+
+ // Number of bones
+ CUSTOM_PROP static int BoneCount
+ {
+ return HumanTrait::BoneCount;
+ }
+
+ // Bone's name
+ CUSTOM_PROP static string[] BoneName
+ {
+ return Scripting::StringVectorToMono( HumanTrait::GetBoneName() );
+ }
+
+ // Return muscle index linked to bone i, dofIndex allow you to choose between X, Y and Z muscle's axis
+ CUSTOM static int MuscleFromBone(int i, int dofIndex){
+ return HumanTrait::MuscleFromBone(i, dofIndex);
+ }
+
+ // Return bone index linked to muscle i
+ CUSTOM static int BoneFromMuscle(int i){
+ return HumanTrait::BoneFromMuscle(i);
+ }
+
+ // Return true if bone i is a required bone.
+ CUSTOM static bool RequiredBone(int i){
+ return HumanTrait::RequiredBone(i);
+ }
+
+ // Number of required bones.
+ CUSTOM_PROP static int RequiredBoneCount
+ {
+ return HumanTrait::RequiredBoneCount();
+ }
+
+ CUSTOM internal static bool HasCollider(Avatar avatar, int i){
+ return HumanTrait::HasCollider(*avatar, i);
+ }
+
+ // Return default minimum values for muscle.
+ CUSTOM static float GetMuscleDefaultMin(int i){
+ return HumanTrait::GetMuscleDefaultMin(i);
+ }
+
+ // Return default maximum values for muscle.
+ CUSTOM static float GetMuscleDefaultMax(int i){
+ return HumanTrait::GetMuscleDefaultMax(i);
+ }
+
+END
+
+CSRAW }
diff --git a/Runtime/Animation/ScriptBindings/AvatarBuilderBindings.txt b/Runtime/Animation/ScriptBindings/AvatarBuilderBindings.txt
new file mode 100644
index 0000000..641af63
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/AvatarBuilderBindings.txt
@@ -0,0 +1,250 @@
+C++RAW
+
+#include "UnityPrefix.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+
+#include "Runtime/Scripting/ScriptingManager.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/ScriptingExportUtility.h"
+
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Animation/AvatarBuilder.h"
+#include "Runtime/Animation/Avatar.h"
+#include "Runtime/Scripting/Scripting.h"
+
+using namespace Unity;
+
+CSRAW
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+STRUCT SkeletonBone
+ CSRAW public string name;
+
+ CSRAW public Vector3 position;
+ CSRAW public Quaternion rotation;
+ CSRAW public Vector3 scale;
+ CSRAW public int transformModified;
+END
+
+C++RAW
+
+struct MonoSkeletonBone {
+ ScriptingStringPtr name;
+ Vector3f position;
+ Quaternionf rotation;
+ Vector3f scale;
+ int transformModified;
+};
+
+void SkeletonBoneToMono (const SkeletonBone &src, MonoSkeletonBone &dest) {
+ dest.name = scripting_string_new(src.m_Name);
+ dest.position = src.m_Position;
+ dest.rotation = src.m_Rotation;
+ dest.scale = src.m_Scale;
+ dest.transformModified = src.m_TransformModified ? 1 : 0;
+}
+
+void SkeletonBoneFromMono (const MonoSkeletonBone &src, SkeletonBone &dest) {
+ dest.m_Name = scripting_cpp_string_for(src.name);
+ dest.m_Position = src.position;
+ dest.m_Rotation = src.rotation;
+ dest.m_Scale = src.scale;
+ dest.m_TransformModified = src.transformModified != 0;
+}
+
+STRUCT HumanLimit
+ CSRAW
+
+ Vector3 m_Min;
+ Vector3 m_Max;
+ Vector3 m_Center;
+ float m_AxisLength;
+ int m_UseDefaultValues;
+
+ public bool useDefaultValues { get { return m_UseDefaultValues != 0; } set { m_UseDefaultValues = value ? 1 : 0; } }
+ public Vector3 min { get { return m_Min; } set { m_Min = value; } }
+ public Vector3 max { get { return m_Max; } set { m_Max = value; } }
+ public Vector3 center { get { return m_Center; } set { m_Center = value; } }
+ public float axisLength { get { return m_AxisLength; } set { m_AxisLength = value; } }
+END
+
+C++RAW
+
+struct MonoHumanLimit {
+
+ Vector3f m_Min;
+ Vector3f m_Max;
+ Vector3f m_Center;
+ float m_AxisLength;
+ int m_UseDefaultValues;
+};
+
+void HumanLimitToMono (const SkeletonBoneLimit &src, MonoHumanLimit &dest) {
+ dest.m_UseDefaultValues = src.m_Modified ? 0 : 1;
+ dest.m_Min = src.m_Min;
+ dest.m_Max = src.m_Max;
+ dest.m_Center = src.m_Value;
+ dest.m_AxisLength = src.m_Length;
+}
+
+void HumanLimitFromMono (const MonoHumanLimit &src, SkeletonBoneLimit &dest) {
+ dest.m_Modified = src.m_UseDefaultValues == 1 ? false : true;
+ dest.m_Min = src.m_Min;
+ dest.m_Max = src.m_Max;
+ dest.m_Value = src.m_Center;
+ dest.m_Length = src.m_AxisLength;
+}
+
+STRUCT HumanBone
+ CSRAW
+ string m_BoneName;
+ string m_HumanName;
+ public HumanLimit limit;
+
+ public string boneName { get { return m_BoneName; } set { m_BoneName = value; } }
+ public string humanName { get { return m_HumanName; } set { m_HumanName = value; } }
+END
+
+C++RAW
+
+struct MonoHumanBone {
+ ScriptingStringPtr m_BoneName;
+ ScriptingStringPtr m_HumanName;
+ MonoHumanLimit m_Limit;
+};
+
+void HumanBoneToMono (const HumanBone &src, MonoHumanBone &dest)
+{
+ dest.m_BoneName = scripting_string_new(src.m_BoneName);
+ dest.m_HumanName = scripting_string_new(src.m_HumanName);
+ HumanLimitToMono(src.m_Limit, dest.m_Limit);
+}
+
+void HumanBoneFromMono (const MonoHumanBone &src, HumanBone &dest)
+{
+ dest.m_BoneName = scripting_cpp_string_for(src.m_BoneName);
+ dest.m_HumanName = scripting_cpp_string_for(src.m_HumanName);
+ HumanLimitFromMono(src.m_Limit, dest.m_Limit);
+}
+
+STRUCT HumanDescription
+ CSRAW
+ public HumanBone[] human;
+ public SkeletonBone[] skeleton;
+
+ float m_ArmTwist;
+ float m_ForeArmTwist;
+ float m_UpperLegTwist;
+ float m_LegTwist;
+ float m_ArmStretch;
+ float m_LegStretch;
+ float m_FeetSpacing;
+
+ public float upperArmTwist { get { return m_ArmTwist; } set { m_ArmTwist = value;} }
+ public float lowerArmTwist { get { return m_ForeArmTwist; } set { m_ForeArmTwist = value;} }
+ public float upperLegTwist { get { return m_UpperLegTwist; } set { m_UpperLegTwist = value;} }
+ public float lowerLegTwist { get { return m_LegTwist; } set { m_LegTwist = value;} }
+ public float armStretch { get { return m_ArmStretch; } set { m_ArmStretch = value;} }
+ public float legStretch { get { return m_LegStretch; } set { m_LegStretch = value;} }
+ public float feetSpacing { get { return m_FeetSpacing; } set { m_FeetSpacing = value;} }
+END
+
+C++RAW
+
+struct MonoHumanDescription {
+ ScriptingArrayPtr m_Human;
+ ScriptingArrayPtr m_Skeleton;
+
+ float m_ArmTwist;
+ float m_ForeArmTwist;
+ float m_UpperLegTwist;
+ float m_LegTwist;
+ float m_ArmStretch;
+ float m_LegStretch;
+ float m_FeetSpacing;
+};
+
+
+void HumanDescriptionToMono (const HumanDescription &src, MonoHumanDescription &dest)
+{
+ if (src.m_Skeleton.size() <= 0)
+ dest.m_Skeleton = CreateEmptyStructArray(MONO_COMMON.skeletonBone);
+ else
+ dest.m_Skeleton = CreateScriptingArray(&src.m_Skeleton[0], src.m_Skeleton.size(), MONO_COMMON.skeletonBone);
+
+ if (src.m_Human.size() <= 0)
+ dest.m_Human = CreateEmptyStructArray(MONO_COMMON.humanBone);
+ else
+ dest.m_Human = CreateScriptingArray(&src.m_Human[0], src.m_Human.size(), MONO_COMMON.humanBone);
+
+ dest.m_ArmTwist = src.m_ArmTwist;
+ dest.m_ForeArmTwist = src.m_ForeArmTwist;
+
+ dest.m_UpperLegTwist = src.m_UpperLegTwist;
+ dest.m_LegTwist = src.m_LegTwist;
+ dest.m_ArmStretch = src.m_ArmStretch;
+ dest.m_LegStretch = src.m_LegStretch;
+ dest.m_FeetSpacing = src.m_FeetSpacing;
+}
+
+void HumanDescriptionFromMono (const MonoHumanDescription &src, HumanDescription &dest)
+{
+ ScriptingStructArrayToVector<SkeletonBone, MonoSkeletonBone>(src.m_Skeleton, dest.m_Skeleton, SkeletonBoneFromMono);
+ ScriptingStructArrayToVector<HumanBone, MonoHumanBone>(src.m_Human, dest.m_Human, HumanBoneFromMono);
+
+ dest.m_ArmTwist = src.m_ArmTwist;
+ dest.m_ForeArmTwist = src.m_ForeArmTwist;
+
+ dest.m_UpperLegTwist = src.m_UpperLegTwist;
+ dest.m_LegTwist = src.m_LegTwist;
+ dest.m_ArmStretch = src.m_ArmStretch;
+ dest.m_LegStretch = src.m_LegStretch;
+ dest.m_FeetSpacing = src.m_FeetSpacing;
+}
+
+CLASS AvatarBuilder
+ CUSTOM static Avatar BuildHumanAvatar(GameObject go, HumanDescription monoHumanDescription) {
+ Avatar* avatar = NEW_OBJECT(Avatar);
+ avatar->Reset();
+
+ HumanDescription humanDescription;
+ HumanDescriptionFromMono(monoHumanDescription, humanDescription);
+
+ AvatarBuilder::Options options;
+ options.avatarType = kHumanoid;
+ options.useMask = true;
+
+ std::string error = AvatarBuilder::BuildAvatar(*avatar, *go, false, humanDescription, options);
+ if(!error.empty())
+ ErrorString(error);
+
+ avatar->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
+
+ return Scripting::ScriptingWrapperFor(avatar);
+ }
+
+ CUSTOM static Avatar BuildGenericAvatar(GameObject go, string rootMotionTransformName) {
+ Avatar* avatar = NEW_OBJECT(Avatar);
+ avatar->Reset();
+
+ HumanDescription humanDescription;
+ humanDescription.m_RootMotionBoneName = rootMotionTransformName.AsUTF8().c_str();
+ std::string error = AvatarBuilder::BuildAvatar(*avatar, *go, false, humanDescription);
+ if(!error.empty())
+ ErrorString(error);
+
+ avatar->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
+
+ return Scripting::ScriptingWrapperFor(avatar);
+ }
+END
+
+CSRAW }
diff --git a/Runtime/Animation/ScriptBindings/RuntimeAnimatorControllerBindings.txt b/Runtime/Animation/ScriptBindings/RuntimeAnimatorControllerBindings.txt
new file mode 100644
index 0000000..98ddbfd
--- /dev/null
+++ b/Runtime/Animation/ScriptBindings/RuntimeAnimatorControllerBindings.txt
@@ -0,0 +1,26 @@
+C++RAW
+#include "UnityPrefix.h"
+#include "Runtime/Mono/MonoManager.h"
+
+CSRAW
+using System;
+using UnityEngine;
+using Object=UnityEngine.Object;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections;
+
+namespace UnityEngine
+{
+
+
+/// Runtime representation of the AnimatorController
+/// Used to change at runtime the AnimatorController of an Animator
+NONSEALED_CLASS public RuntimeAnimatorController : Object
+END
+
+
+
+
+
+CSRAW }
diff --git a/Runtime/Animation/StreamedClipBuilder.cpp b/Runtime/Animation/StreamedClipBuilder.cpp
new file mode 100644
index 0000000..415baa3
--- /dev/null
+++ b/Runtime/Animation/StreamedClipBuilder.cpp
@@ -0,0 +1,272 @@
+#include "UnityPrefix.h"
+#include "StreamedClipBuilder.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/mecanim/memory.h"
+
+struct BuildCurveKey
+{
+ float time;
+ int curveIndex;
+ float coeff[4];
+
+ friend bool operator < (const BuildCurveKey& lhs, const BuildCurveKey& rhs)
+ {
+ // Sort by time primarily
+ if (lhs.time != rhs.time)
+ return lhs.time < rhs.time;
+ // for same time, Sort by curve index. This reduces cache trashing when sampling the clip
+ else
+ return lhs.curveIndex < rhs.curveIndex;
+ }
+};
+
+struct StreamedClipBuilder
+{
+ StreamedClipBuilder(): allKeys(kMemTempAlloc) {}
+
+ dynamic_array<BuildCurveKey> allKeys;
+ int curveCount;
+};
+
+StreamedClipBuilder* CreateStreamedClipBuilder(UInt32 curveCount, UInt32 keyCount)
+{
+ StreamedClipBuilder* builder = UNITY_NEW(StreamedClipBuilder, kMemTempAlloc);
+ builder->allKeys.reserve(keyCount);
+ builder->curveCount = curveCount;
+ return builder;
+}
+
+void DestroyStreamedClipBuilder(StreamedClipBuilder* builder)
+{
+ UNITY_DELETE(builder, kMemTempAlloc);
+}
+
+template<typename T>
+void ConvertCacheToBuildKeys(typename AnimationCurveTpl<T>::Cache& cache, int curveIndex, StreamedClipBuilder* builder)
+{
+ int elements = sizeof(T)/sizeof(float);
+ //Loop over number of floats in curveType
+ for(int e = 0; e < elements; e++)
+ {
+ BuildCurveKey& key = builder->allKeys.push_back();
+
+ key.time = cache.time;
+ key.curveIndex = curveIndex+e;
+ key.coeff[0] = cache.coeff[0][e];
+ key.coeff[1] = cache.coeff[1][e];
+ key.coeff[2] = cache.coeff[2][e];
+ key.coeff[3] = cache.coeff[3][e];
+ }
+}
+
+template<>
+void ConvertCacheToBuildKeys<float>(AnimationCurveTpl<float>::Cache& cache, int curveIndex, StreamedClipBuilder* builder)
+{
+ BuildCurveKey& key = builder->allKeys.push_back();
+
+ key.time = cache.time;
+ key.curveIndex = curveIndex;
+ key.coeff[0] = cache.coeff[0];
+ key.coeff[1] = cache.coeff[1];
+ key.coeff[2] = cache.coeff[2];
+ key.coeff[3] = cache.coeff[3];
+}
+
+const float kFirstClampKeyframe = -FLT_MAX;
+
+template<class T>
+void AddCurveToStreamedClip(StreamedClipBuilder* builder, int curveIndex, const AnimationCurveTpl<T>& curve)
+{
+ for(int k = -1; k < curve.GetKeyCount(); k++)
+ {
+ typename AnimationCurveTpl<T>::Cache cache;
+
+ // Last key needs to be specifically prepared as a constant value
+ // Use clamp cache function to get same functionaliy as AnimationCurve.EvaluateClamp
+ if (k == curve.GetKeyCount()-1)
+ {
+ cache.time = curve.GetKey(k).time;
+ cache.coeff[0] = cache.coeff[1] = cache.coeff[2] = Zero<T>();
+ cache.coeff[3] = curve.GetKey(k).value;
+ }
+ // A special first key needs to be created so that all values can be sampled before their first keyframe.
+ else if (k == -1)
+ {
+ // We already have a key at the first firstClampKeyframe no need to duplicate it.
+ if (kFirstClampKeyframe == curve.GetKey(0).time)
+ continue;
+
+ cache.time = kFirstClampKeyframe;
+ cache.coeff[0] = cache.coeff[1] = cache.coeff[2] = Zero<T>();
+ cache.coeff[3] = curve.GetKey(0).value;
+ }
+ // Use CalculateCacheData for all normal curve segements
+ else
+ curve.CalculateCacheData(cache, k, k+1, 0.0F);
+
+ ConvertCacheToBuildKeys<T>(cache, curveIndex, builder);
+ }
+}
+
+void AddIntegerCurveToStreamedClip(StreamedClipBuilder* builder, int curveIndex, float* time, int* value, int count)
+{
+ for (int k = 0; k < count; ++k)
+ {
+ AnimationCurveTpl<float>::Cache cache;
+
+ // Last key needs to be specifically prepared as a constant value
+ // Use clamp cache function to get same functionaliy as AnimationCurve.EvaluateClamp
+ cache.time = (k == 0) ? kFirstClampKeyframe : time[k];
+ cache.coeff[0] = cache.coeff[1] = cache.coeff[2] = 0.0f;
+ cache.coeff[3] = value[k];
+
+ ConvertCacheToBuildKeys<float>(cache, curveIndex, builder);
+ }
+}
+
+
+
+template void AddCurveToStreamedClip<float>(StreamedClipBuilder* builder, int curveIndex, const AnimationCurveTpl<float>& curve);
+template void AddCurveToStreamedClip<Vector3f>(StreamedClipBuilder* builder, int curveIndex, const AnimationCurveTpl<Vector3f>& curve);
+template void AddCurveToStreamedClip<Quaternionf>(StreamedClipBuilder* builder, int curveIndex, const AnimationCurveTpl<Quaternionf>& curve);
+
+template<class T>
+T& PushData (dynamic_array<UInt8>& output)
+{
+ output.resize_uninitialized(output.size() + sizeof(T));
+ return *reinterpret_cast<T*> (&output[output.size() - sizeof(T)]);
+}
+
+void CreateStreamClipConstant (StreamedClipBuilder* builder, mecanim::animation::StreamedClip& clip, mecanim::memory::Allocator& alloc)
+{
+ Assert(clip.curveCount == 0);
+ Assert(clip.data.IsNull());
+
+ std::sort(builder->allKeys.begin(), builder->allKeys.end());
+
+ dynamic_array<UInt8> streamData;
+ streamData.reserve((builder->allKeys.size()+1) * (sizeof(mecanim::animation::CurveKey) + sizeof(mecanim::animation::CurveTimeData)));
+
+
+ // Generate the curvedata stream
+ float currentTime = -std::numeric_limits<float>::infinity();
+ for (int i=0;i<builder->allKeys.size();)
+ {
+ currentTime = builder->allKeys[i].time;
+ mecanim::animation::CurveTimeData& timeData = PushData<mecanim::animation::CurveTimeData> (streamData);
+ timeData.time = currentTime;
+
+ int count = 0;
+ while (i < builder->allKeys.size() && builder->allKeys[i].time == currentTime)
+ {
+ mecanim::animation::CurveKey& curveKey = PushData<mecanim::animation::CurveKey>(streamData);
+ curveKey.curveIndex = builder->allKeys[i].curveIndex;
+ memcpy(curveKey.coeff, builder->allKeys[i].coeff, sizeof(curveKey.coeff));
+
+ i++;
+ count++;
+ }
+
+ timeData.count = count;
+ }
+
+ // Make sure that we do not sample beyond the last actual key by adding an infinity key.
+ mecanim::animation::CurveTimeData& timeData = PushData<mecanim::animation::CurveTimeData> (streamData);
+ timeData.time = std::numeric_limits<float>::infinity();
+ timeData.count = 0;
+
+ clip.dataSize = streamData.size() / sizeof(mecanim::uint32_t);
+ clip.data = alloc.ConstructArray<mecanim::uint32_t> (clip.dataSize);
+ memcpy(clip.data.Get(), streamData.begin(), streamData.size());
+ clip.curveCount = builder->curveCount;
+}
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+typedef AnimationCurve::Keyframe Keyframe;
+
+static float Evaluate0 (const mecanim::animation::StreamedClip& clip, mecanim::animation::StreamedClipMemory& memory, float time)
+{
+ float output;
+ SampleClip(clip, memory, time, &output);
+
+ return output;
+}
+
+typedef AnimationCurveVec3::Keyframe KeyframeVec3;
+static Vector3f Evaluate3 (const mecanim::animation::StreamedClip& clip, mecanim::animation::StreamedClipMemory& memory, float time)
+{
+ Vector3f output;
+ SampleClip(clip, memory, time, reinterpret_cast<float*>(&output));
+
+ return output;
+}
+
+SUITE (StreamedClipBuilderTests)
+{
+TEST (StreamedClipBuilder_StreamedClipEvaluation)
+{
+ mecanim::memory::MecanimAllocator alloc(kMemTempAlloc);
+
+ AnimationCurve curve;
+ curve.AddKeyBackFast(Keyframe(0.5F, 0.0F));
+ curve.AddKeyBackFast(Keyframe(1.0F, 1.0F));
+ curve.AddKeyBackFast(Keyframe(2.0F, -1.0F));
+
+ StreamedClipBuilder* builder = CreateStreamedClipBuilder(1, curve.GetKeyCount());
+ AddCurveToStreamedClip(builder, 0, curve);
+ mecanim::animation::StreamedClip streamclip;
+ CreateStreamClipConstant (builder, streamclip, alloc);
+
+ mecanim::animation::StreamedClipMemory memory;
+ CreateStreamedClipMemory(streamclip, memory, alloc);
+
+ CHECK_EQUAL(curve.EvaluateClamp(-5.0), Evaluate0(streamclip, memory, -5.0F));
+ CHECK_EQUAL(curve.EvaluateClamp(1.0F), Evaluate0(streamclip, memory, 1.0F));
+ CHECK_EQUAL(curve.EvaluateClamp(0.0F), Evaluate0(streamclip, memory, 0.0F));
+ CHECK_EQUAL(curve.EvaluateClamp(1.5F), Evaluate0(streamclip, memory, 1.5F));
+ CHECK_EQUAL(curve.EvaluateClamp(2.0F), Evaluate0(streamclip, memory, 2.0F));
+ CHECK_EQUAL(curve.EvaluateClamp(0.1F), Evaluate0(streamclip, memory, 0.1F));
+ CHECK_EQUAL(curve.EvaluateClamp(100.0F), Evaluate0(streamclip, memory, 100.0F));
+ CHECK_EQUAL(curve.EvaluateClamp(-19), Evaluate0(streamclip, memory, -19.0F));
+
+ DestroyStreamedClipMemory (memory, alloc);
+ DestroyStreamedClip (streamclip, alloc);
+ DestroyStreamedClipBuilder (builder);
+}
+
+
+TEST (StreamedClipEvaluationVector3)
+{
+ mecanim::memory::MecanimAllocator alloc(kMemTempAlloc);
+
+ AnimationCurveVec3 curve;
+ curve.AddKeyBackFast(KeyframeVec3(0.5F, Vector3f(0.0,1.0,2.0)));
+ curve.AddKeyBackFast(KeyframeVec3(1.0F, Vector3f(3.0,0.0,4.0)));
+ curve.AddKeyBackFast(KeyframeVec3(2.0F, Vector3f(0.0,-1.0,-2.0)));
+
+ StreamedClipBuilder* builder = CreateStreamedClipBuilder(3, curve.GetKeyCount()*3);
+ AddCurveToStreamedClip(builder, 0, curve);
+ mecanim::animation::StreamedClip streamclip;
+ CreateStreamClipConstant (builder, streamclip, alloc);
+
+ mecanim::animation::StreamedClipMemory memory;
+ CreateStreamedClipMemory(streamclip, memory, alloc);
+
+ CHECK(curve.EvaluateClamp(-5.0) == Evaluate3(streamclip, memory, -5.0F));
+ CHECK(curve.EvaluateClamp(1.0F) == Evaluate3(streamclip, memory, 1.0F));
+ CHECK(curve.EvaluateClamp(0.0F) == Evaluate3(streamclip, memory, 0.0F));
+ CHECK(curve.EvaluateClamp(1.5F) == Evaluate3(streamclip, memory, 1.5F));
+ CHECK(curve.EvaluateClamp(2.0F) == Evaluate3(streamclip, memory, 2.0F));
+ CHECK(curve.EvaluateClamp(0.1F) == Evaluate3(streamclip, memory, 0.1F));
+ CHECK(curve.EvaluateClamp(100.0F) == Evaluate3(streamclip, memory, 100.0F));
+ CHECK(curve.EvaluateClamp(-19) == Evaluate3(streamclip, memory, -19.0F));
+
+ DestroyStreamedClipMemory (memory, alloc);
+ DestroyStreamedClip (streamclip, alloc);
+ DestroyStreamedClipBuilder (builder);
+}
+}
+#endif
diff --git a/Runtime/Animation/StreamedClipBuilder.h b/Runtime/Animation/StreamedClipBuilder.h
new file mode 100644
index 0000000..7ae55d1
--- /dev/null
+++ b/Runtime/Animation/StreamedClipBuilder.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "Runtime/Math/AnimationCurve.h"
+#include "Runtime/mecanim/animation/streamedclip.h"
+
+struct StreamedClipBuilder;
+
+StreamedClipBuilder* CreateStreamedClipBuilder(UInt32 curveCount, UInt32 keyCount);
+void DestroyStreamedClipBuilder(StreamedClipBuilder* builder);
+
+template<class T>
+void AddCurveToStreamedClip(StreamedClipBuilder* builder, int curveIndex, const AnimationCurveTpl<T>& curve);
+
+void AddIntegerCurveToStreamedClip(StreamedClipBuilder* builder, int curveIndex, float* time, int* value, int count);
+
+void CreateStreamClipConstant (StreamedClipBuilder* builder, mecanim::animation::StreamedClip& clip, mecanim::memory::Allocator& alloc);