diff options
Diffstat (limited to 'Runtime/Animation')
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); |