Ver Fonte

Added a way to sample a single frame of animation using Animation.Sample
Don't update mapped SO's that aren't attached to bones, if no changes were made by the animation (was resetting them to identity)
Fixing conflicts between record and playback within animation editor (changing frame during record won't cause keyframes to be added/updated)

BearishSun há 9 anos atrás
pai
commit
0f25ea40a0

+ 21 - 1
Source/BansheeCore/Include/BsAnimation.h

@@ -56,6 +56,17 @@ namespace BansheeEngine
 		bool stopped = false;
 	};
 
+	/** Type of playback for animation clips. */
+	enum class AnimPlaybackType
+	{
+		/** Play back the animation normally by advancing time. */
+		Normal,
+		/** Sample only a single frame from the animation. */
+		Sampled,
+		/** Do not play the animation. */
+		None
+	};
+
 	/** Internal information about a single playing animation clip within Animation. */
 	struct AnimationClipInfo
 	{
@@ -64,7 +75,8 @@ namespace BansheeEngine
 
 		HAnimationClip clip;
 		AnimationClipState state;
-		
+		AnimPlaybackType playbackType;
+
 		float fadeDirection;
 		float fadeTime;
 		float fadeLength;
@@ -307,6 +319,14 @@ namespace BansheeEngine
 		 */
 		void crossFade(const HAnimationClip& clip, float fadeLength);
 
+		/**
+		 * Samples an animation clip at the specified time, displaying only that particular frame without further playback.
+		 *
+		 * @param[in] clip	Animation clip to sample.
+		 * @param[in] time	Time to sample the clip at.
+		 */
+		void sample(const HAnimationClip& clip, float time);
+
 		/** 
 		 * Stops playing all animations on the provided layer. Specify -1 to stop animation on the main layer 
 		 * (non-additive animations). 

+ 58 - 7
Source/BansheeCore/Source/BsAnimation.cpp

@@ -9,11 +9,13 @@
 namespace BansheeEngine
 {
 	AnimationClipInfo::AnimationClipInfo()
-		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), curveVersion(0), layerIdx((UINT32)-1), stateIdx((UINT32)-1)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), curveVersion(0), layerIdx((UINT32)-1)
+		, stateIdx((UINT32)-1), playbackType(AnimPlaybackType::Normal)
 	{ }
 
 	AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
-		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx((UINT32)-1), stateIdx((UINT32)-1)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx((UINT32)-1)
+		, stateIdx((UINT32)-1), playbackType(AnimPlaybackType::Normal)
 	{ }
 
 	Blend1DInfo::Blend1DInfo(UINT32 numClips)
@@ -112,7 +114,7 @@ namespace BansheeEngine
 		Vector<AnimationClipInfo>& clipInfos, const Vector<AnimatedSceneObject>& sceneObjects)
 	{
 		this->skeleton = skeleton;
-		this->skeletonMask = skeletonMask;
+		this->skeletonMask = mask;
 
 		// Note: I could avoid having a separate allocation for LocalSkeletonPoses and use the same buffer as the rest
 		// of AnimationProxy
@@ -362,7 +364,7 @@ namespace BansheeEngine
 					if (isClipValid)
 					{
 						state.curves = clipInfo.clip->getCurves();
-						state.disabled = false;
+						state.disabled = clipInfo.playbackType == AnimPlaybackType::None;
 					}
 					else
 					{
@@ -497,6 +499,9 @@ namespace BansheeEngine
 			state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
 			state.weight = clipInfo.state.weight;
 			state.time = clipInfo.state.time;
+
+			bool isLoaded = clipInfo.clip.isLoaded();
+			state.disabled = !isLoaded || clipInfo.playbackType == AnimPlaybackType::None;
 		}
 	}
 
@@ -541,6 +546,9 @@ namespace BansheeEngine
 		{
 			AnimationState& state = layers[clipInfo.layerIdx].states[clipInfo.stateIdx];
 			state.time = clipInfo.state.time;
+
+			bool isLoaded = clipInfo.clip.isLoaded();
+			state.disabled = !isLoaded || clipInfo.playbackType == AnimPlaybackType::None;
 		}
 	}
 
@@ -616,6 +624,7 @@ namespace BansheeEngine
 			clipInfo->state.speed = mDefaultSpeed;
 			clipInfo->state.weight = 1.0f;
 			clipInfo->state.wrapMode = mDefaultWrapMode;
+			clipInfo->playbackType = AnimPlaybackType::Normal;
 		}
 
 		mDirty |= AnimDirtyStateFlag::Value;
@@ -649,6 +658,7 @@ namespace BansheeEngine
 				clipInfo->fadeLength = fadeLength;
 			}
 
+			clipInfo->playbackType = AnimPlaybackType::Normal;
 			mDirty |= AnimDirtyStateFlag::Value;
 		}
 	}
@@ -739,6 +749,8 @@ namespace BansheeEngine
 					clipInfo->state.weight = t;
 				else
 					clipInfo->state.weight = 0.0f;
+
+				clipInfo->playbackType = AnimPlaybackType::Normal;
 			}
 		}
 
@@ -755,6 +767,8 @@ namespace BansheeEngine
 			topLeftClipInfo->state.speed = 0.0f;
 			topLeftClipInfo->state.weight = (1.0f - t.x) * (1.0f - t.y);
 			topLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+
+			topLeftClipInfo->playbackType = AnimPlaybackType::Normal;
 		}
 
 		AnimationClipInfo* topRightClipInfo = addClip(info.topRightClip, (UINT32)-1, false);
@@ -765,6 +779,8 @@ namespace BansheeEngine
 			topLeftClipInfo->state.speed = 0.0f;
 			topRightClipInfo->state.weight = t.x * (1.0f - t.y);
 			topRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+
+			topRightClipInfo->playbackType = AnimPlaybackType::Normal;
 		}
 
 		AnimationClipInfo* botLeftClipInfo = addClip(info.botLeftClip, (UINT32)-1, false);
@@ -775,6 +791,8 @@ namespace BansheeEngine
 			topLeftClipInfo->state.speed = 0.0f;
 			botLeftClipInfo->state.weight = (1.0f - t.x) * t.y;
 			botLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+
+			botLeftClipInfo->playbackType = AnimPlaybackType::Normal;
 		}
 
 		AnimationClipInfo* botRightClipInfo = addClip(info.botRightClip, (UINT32)-1, false);
@@ -785,6 +803,8 @@ namespace BansheeEngine
 			botRightClipInfo->state.speed = 0.0f;
 			botRightClipInfo->state.weight = t.x * t.y;
 			botRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+
+			botRightClipInfo->playbackType = AnimPlaybackType::Normal;
 		}
 
 		mDirty |= AnimDirtyStateFlag::Value;
@@ -806,6 +826,7 @@ namespace BansheeEngine
 			clipInfo->state.speed = mDefaultSpeed;
 			clipInfo->state.weight = 1.0f;
 			clipInfo->state.wrapMode = mDefaultWrapMode;
+			clipInfo->playbackType = AnimPlaybackType::Normal;
 
 			// Set up fade lengths
 			clipInfo->fadeDirection = 1.0f;
@@ -837,6 +858,21 @@ namespace BansheeEngine
 		mDirty |= AnimDirtyStateFlag::Value;
 	}
 
+	void Animation::sample(const HAnimationClip& clip, float time)
+	{
+		AnimationClipInfo* clipInfo = addClip(clip, (UINT32)-1);
+		if (clipInfo != nullptr)
+		{
+			clipInfo->state.time = time;
+			clipInfo->state.speed = 0.0f;
+			clipInfo->state.weight = 1.0f;
+			clipInfo->state.wrapMode = mDefaultWrapMode;
+			clipInfo->playbackType = AnimPlaybackType::Sampled;
+		}
+
+		mDirty |= AnimDirtyStateFlag::Value;
+	}
+
 	void Animation::stop(UINT32 layer)
 	{
 		bs_frame_mark();
@@ -907,7 +943,7 @@ namespace BansheeEngine
 		{
 			AnimationClipInfo& newInfo = mClipInfos.back();
 			newInfo.clip = clip;
-			newInfo.layerIdx = layer;
+			newInfo.state.layer = layer;
 
 			output = &newInfo;
 		}
@@ -973,12 +1009,19 @@ namespace BansheeEngine
 
 	void Animation::setState(const HAnimationClip& clip, AnimationClipState state)
 	{
+		if (state.layer == 0)
+			state.layer = (UINT32)-1;
+		else
+			state.layer -= 1;
+
 		AnimationClipInfo* clipInfo = addClip(clip, state.layer, false);
 
 		if (clipInfo == nullptr)
 			return;
 
 		clipInfo->state = state;
+		clipInfo->playbackType = AnimPlaybackType::Normal;
+
 		mDirty |= AnimDirtyStateFlag::Value;
 	}
 
@@ -1162,6 +1205,13 @@ namespace BansheeEngine
 			}
 		}
 
+		// Disable sampled animations
+		for (auto& clipInfo : mClipInfos)
+		{
+			if (clipInfo.playbackType == AnimPlaybackType::Sampled)
+				clipInfo.playbackType = AnimPlaybackType::None;
+		}
+
 		mDirty = AnimDirtyState();
 	}
 
@@ -1262,12 +1312,13 @@ namespace BansheeEngine
 			}
 			else
 			{
+				if (mAnimProxy->sceneObjectPose.hasOverride[i])
+					continue;
+
 				so->setPosition(mAnimProxy->sceneObjectPose.positions[i]);
 				so->setRotation(mAnimProxy->sceneObjectPose.rotations[i]);
 				so->setScale(mAnimProxy->sceneObjectPose.scales[i]);
 			}
-
-			soInfo.hash = so->getTransformHash();
 		}
 
 		// Must ensure that clip in the proxy and current primary clip are the same

+ 5 - 0
Source/BansheeCore/Source/BsAnimationManager.cpp

@@ -182,6 +182,8 @@ namespace BansheeEngine
 			}
 
 			// Update mapped scene objects
+			memset(anim->sceneObjectPose.hasOverride, 1, sizeof(bool) * anim->numSceneObjects);
+
 			for(UINT32 i = 0; i < anim->numSceneObjects; i++)
 			{
 				const AnimatedSceneObjectInfo& soInfo = anim->sceneObjectInfos[i];
@@ -203,6 +205,7 @@ namespace BansheeEngine
 					{
 						const TAnimationCurve<Vector3>& curve = state.curves->position[curveIdx].curve;
 						anim->sceneObjectPose.positions[curveIdx] = curve.evaluate(state.time, state.positionCaches[curveIdx], state.loop);
+						anim->sceneObjectPose.hasOverride[curveIdx] = false;
 					}
 				}
 
@@ -213,6 +216,7 @@ namespace BansheeEngine
 						const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve;
 						anim->sceneObjectPose.rotations[curveIdx] = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop);
 						anim->sceneObjectPose.rotations[curveIdx].normalize();
+						anim->sceneObjectPose.hasOverride[curveIdx] = false;
 					}
 				}
 
@@ -222,6 +226,7 @@ namespace BansheeEngine
 					{
 						const TAnimationCurve<Vector3>& curve = state.curves->scale[curveIdx].curve;
 						anim->sceneObjectPose.scales[curveIdx] = curve.evaluate(state.time, state.scaleCaches[curveIdx], state.loop);
+						anim->sceneObjectPose.hasOverride[curveIdx] = false;
 					}
 				}
 			}

+ 3 - 0
Source/BansheeEngine/Include/BsCAnimation.h

@@ -59,6 +59,9 @@ namespace BansheeEngine
 		/** @copydoc Animation::crossFade */
 		void crossFade(const HAnimationClip& clip, float fadeLength);
 
+		/** @copydoc Animation::sample */
+		void sample(const HAnimationClip& clip, float time);
+
 		/** @copydoc Animation::stop */
 		void stop(UINT32 layer);
 

+ 6 - 0
Source/BansheeEngine/Source/BsCAnimation.cpp

@@ -78,6 +78,12 @@ namespace BansheeEngine
 			mInternal->crossFade(clip, fadeLength);
 	}
 
+	void CAnimation::sample(const HAnimationClip& clip, float time)
+	{
+		if (mInternal != nullptr)
+			mInternal->sample(clip, time);
+	}
+
 	void CAnimation::stop(UINT32 layer)
 	{
 		if (mInternal != nullptr)

+ 29 - 10
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -80,10 +80,18 @@ namespace BansheeEditor
             }
             else if (state == State.Recording)
             {
-                float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
-                if(RecordState(time))
-                    guiCurveEditor.Redraw();
+                if (!delayRecord)
+                {
+                    float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
+                    if (RecordState(time))
+                    {
+                        ApplyClipChanges();
+                        guiCurveEditor.Redraw();
+                    }
+                }
             }
+
+            delayRecord = false;
         }
 
         private void OnDestroy()
@@ -448,6 +456,8 @@ namespace BansheeEditor
                 SwitchState(State.Normal);
 
                 ApplyClipChanges();
+                PreviewFrame(currentFrameIdx);
+
                 EditorApplication.SetProjectDirty();
             };
             guiCurveEditor.OnClicked += () =>
@@ -824,6 +834,7 @@ namespace BansheeEditor
 
         private State state = State.Empty;
         private SerializedSceneObject soState;
+        private bool delayRecord = false;
 
         /// <summary>
         /// Transitions the window into a different state. Caller must validate state transitions.
@@ -963,8 +974,11 @@ namespace BansheeEditor
         private void StartRecord()
         {
             float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
-            if(RecordState(time))
+            if (RecordState(time))
+            {
+                ApplyClipChanges();
                 guiCurveEditor.Redraw();
+            }
 
             recordButton.Value = true;
         }
@@ -1024,7 +1038,7 @@ namespace BansheeEditor
                             for (int i = 0; i < 2; i++)
                             {
                                 float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
-                                if (!MathEx.ApproxEquals(value[i], curveVal))
+                                if (!MathEx.ApproxEquals(value[i], curveVal, 0.001f))
                                 {
                                     addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, value[i]);
                                     changesMade = true;
@@ -1039,7 +1053,7 @@ namespace BansheeEditor
                             for (int i = 0; i < 3; i++)
                             {
                                 float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
-                                if (!MathEx.ApproxEquals(value[i], curveVal))
+                                if (!MathEx.ApproxEquals(value[i], curveVal, 0.001f))
                                 { 
                                     addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, value[i]);
                                     changesMade = true;
@@ -1056,7 +1070,7 @@ namespace BansheeEditor
                                 for (int i = 0; i < 4; i++)
                                 {
                                     float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
-                                    if (!MathEx.ApproxEquals(value[i], curveVal))
+                                    if (!MathEx.ApproxEquals(value[i], curveVal, 0.001f))
                                     { 
                                         addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, value[i]);
                                         changesMade = true;
@@ -1070,7 +1084,7 @@ namespace BansheeEditor
                                 for (int i = 0; i < 4; i++)
                                 {
                                     float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
-                                    if (!MathEx.ApproxEquals(value[i], curveVal))
+                                    if (!MathEx.ApproxEquals(value[i], curveVal, 0.001f))
                                     { 
                                         addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, value[i]);
                                         changesMade = true;
@@ -1086,7 +1100,7 @@ namespace BansheeEditor
                             for (int i = 0; i < 4; i++)
                             {
                                 float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
-                                if (!MathEx.ApproxEquals(value[i], curveVal))
+                                if (!MathEx.ApproxEquals(value[i], curveVal, 0.001f))
                                 { 
                                     addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, value[i]);
                                     changesMade = true;
@@ -1123,7 +1137,7 @@ namespace BansheeEditor
                             float value = property.GetValue<float>();
 
                             float curveVal = KVP.Value.curveInfos[0].curve.Evaluate(time);
-                            if (!MathEx.ApproxEquals(value, curveVal))
+                            if (!MathEx.ApproxEquals(value, curveVal, 0.001f))
                             { 
                                 addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, value);
                                 changesMade = true;
@@ -1766,6 +1780,11 @@ namespace BansheeEditor
         {
             SetCurrentFrame(frameIdx);
             PreviewFrame(currentFrameIdx);
+
+            // HACK: Skip checking for record changes this frame, to give the preview a chance to update, otherwise
+            // the changes would be detected any time a frame is delayed. A proper fix for this would be to force the
+            // animation to be evaluated synchronously when PreviewFrame is called.
+            delayRecord = true;
         }
 
         /// <summary>

+ 25 - 4
Source/MBansheeEngine/Animation/Animation.cs

@@ -296,6 +296,22 @@ namespace BansheeEngine
             }
         }
 
+        /// <summary>
+        /// Samples an animation clip at the specified time, displaying only that particular frame without further playback.
+        /// </summary>
+        /// <param name="clip">Animation clip to sample.</param>
+        /// <param name="time">Time to sample the clip at.</param>
+        public void Sample(AnimationClip clip, float time)
+        {
+            switch (state)
+            {
+                case State.Active:
+                case State.EditorActive:
+                    _native.Sample(clip, time);
+                    break;
+            }
+        }
+
         /// <summary>
         /// Stops playing all animations on the provided layer.
         /// </summary>
@@ -387,11 +403,16 @@ namespace BansheeEngine
             switch (state)
             {
                 case State.EditorActive:
-                    AnimationClipState clipState = AnimationClipState.Create();
-                    clipState.time = startTime;
-                    clipState.speed = freeze ? 0.0f : 1.0f;
+                    if (freeze)
+                        Sample(clip, startTime);
+                    else
+                    {
+                        AnimationClipState clipState = AnimationClipState.Create();
+                        clipState.time = startTime;
+
+                        SetState(clip, clipState);
+                    }
 
-                    SetState(clip, clipState);
                     RefreshClipMappings();
 
                     break;

+ 12 - 0
Source/MBansheeEngine/Animation/Interop/NativeAnimation.cs

@@ -83,6 +83,15 @@ namespace BansheeEngine
             Internal_CrossFade(mCachedPtr, clipPtr, fadeLength);
         }
 
+        public void Sample(AnimationClip clip, float time)
+        {
+            IntPtr clipPtr = IntPtr.Zero;
+            if (clip != null)
+                clipPtr = clip.GetCachedPtr();
+
+            Internal_Sample(mCachedPtr, clipPtr, time);
+        }
+
         public void Stop(int layer)
         {
             Internal_Stop(mCachedPtr, layer);
@@ -185,6 +194,9 @@ namespace BansheeEngine
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_CrossFade(IntPtr thisPtr, IntPtr clipPtr, float fadeLength);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Sample(IntPtr thisPtr, IntPtr clipPtr, float time);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Stop(IntPtr thisPtr, int layer);
 

+ 1 - 0
Source/SBansheeEngine/Include/BsScriptAnimation.h

@@ -46,6 +46,7 @@ namespace BansheeEngine
 		static void internal_Blend1D(ScriptAnimation* thisPtr, MonoObject* info, float t);
 		static void internal_Blend2D(ScriptAnimation* thisPtr, MonoObject* info, Vector2* t);
 		static void internal_CrossFade(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float fadeLength);
+		static void internal_Sample(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float time);
 
 		static void internal_Stop(ScriptAnimation* thisPtr, UINT32 layer);
 		static void internal_StopAll(ScriptAnimation* thisPtr);

+ 10 - 0
Source/SBansheeEngine/Source/BsScriptAnimation.cpp

@@ -40,6 +40,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_Blend1D", &ScriptAnimation::internal_Blend1D);
 		metaData.scriptClass->addInternalCall("Internal_Blend2D", &ScriptAnimation::internal_Blend2D);
 		metaData.scriptClass->addInternalCall("Internal_CrossFade", &ScriptAnimation::internal_CrossFade);
+		metaData.scriptClass->addInternalCall("Internal_Sample", &ScriptAnimation::internal_Sample);
 
 		metaData.scriptClass->addInternalCall("Internal_Stop", &ScriptAnimation::internal_Stop);
 		metaData.scriptClass->addInternalCall("Internal_StopAll", &ScriptAnimation::internal_StopAll);
@@ -132,6 +133,15 @@ namespace BansheeEngine
 		thisPtr->getInternal()->crossFade(nativeClip, fadeLength);
 	}
 
+	void ScriptAnimation::internal_Sample(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float time)
+	{
+		HAnimationClip nativeClip;
+		if (clip != nullptr)
+			nativeClip = clip->getHandle();
+
+		thisPtr->getInternal()->sample(nativeClip, time);
+	}
+
 	void ScriptAnimation::internal_Stop(ScriptAnimation* thisPtr, UINT32 layer)
 	{
 		thisPtr->getInternal()->stop(layer);