Explorar o código

Animation curves now cache data to speed up sequential evaluations

BearishSun %!s(int64=9) %!d(string=hai) anos
pai
achega
f67d045aa2

+ 1 - 0
Source/BansheeCore/CMakeSources.cmake

@@ -500,6 +500,7 @@ set(BS_BANSHEECORE_INC_ANIMATION
 	"Include/BsAnimationClip.h"
 	"Include/BsSkeleton.h"
 	"Include/BsAnimation.h"
+	"Include/BsAnimationInstance.h"
 )
 
 set(BS_BANSHEECORE_SRC_ANIMATION

+ 0 - 12
Source/BansheeCore/Include/BsAnimation.h

@@ -10,17 +10,5 @@ namespace BansheeEngine
 	 *  @{
 	 */
 
-	struct AnimationInstanceData
-	{
-	public:
-		AnimationInstanceData();
-
-		float time;
-	private:
-		template <class T> friend class TAnimationCurve;
-
-		mutable UINT32 cachedKey;
-	};
-
 	/** @} */
 }

+ 67 - 7
Source/BansheeCore/Include/BsAnimationCurve.h

@@ -3,22 +3,28 @@
 #pragma once
 
 #include "BsCorePrerequisites.h"
+#include "BsAnimationInstance.h"
 
 namespace BansheeEngine
 {
-	/** @addtogroup Animation
+	/** @addtogroup Animation-Internal
 	 *  @{
 	 */
 
+	/** Animation keyframe, represented as an endpoint of a cubic hermite spline. */
 	template <class T>
 	struct TKeyframe
 	{
-		T value;
-		T inTangent;
-		T outTangent;
-		float time;
+		T value; /**< Value of the key. */
+		T inTangent; /**< Input tangent (going from the previous key to this one) of the key. */
+		T outTangent; /**< Output tangent (going from this key to next one) of the key. */
+		float time; /**< Position of the key along the animation spline. */
 	};
 
+	/**
+	 * Animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve. The
+	 * spline can be evaluated at any time, and uses caching to speed up multiple sequential evaluations.
+	 */
 	template <class T>
 	class BS_CORE_EXPORT TAnimationCurve
 	{
@@ -27,12 +33,66 @@ namespace BansheeEngine
 
 		TAnimationCurve(const Vector<KeyFrame>& keyframes);
 
-		T evaluate(const AnimationInstanceData& animInstance, bool loop = true);
+		/**
+		 * Evaluate the animation curve using caching. Caching can significantly speed of evaluation if the evaluation
+		 * happens sequential order (which should be true for most curves). If evaluation is not happening in sequential
+		 * order using the non-caching version of evaluate() might yield better performance.
+		 *
+		 * @param[i]	animInstance	Animation instance data holding the time to evaluate the curve at, and any cached
+		 *								data from previous requests. Caller should ensure to maintain a persistent instance
+		 *								of this data for every animation using this curve in order to ensure cache is
+		 *								maintained.
+		 * @param[in]	loop			If true the curve will loop when it goes past the end or beggining. Otherwise the
+		 *								curve value will be clamped.
+		 @return						Interpolated value from the curve at provided time.
+		 */
+		T evaluate(const AnimationInstanceData<T>& animInstance, bool loop = true);
+
+		/**
+		 * Evaluate the animation curve at the specified time. If evaluating multiple values in a sequential order consider
+		 * using the cached version of evaluate() for better performance.
+		 *
+		 * @param[i]	time	Time to evaluate the curve at.		
+		 * @param[in]	loop	If true the curve will loop when it goes past the end or beggining. Otherwise the curve 
+		 *						value will be clamped.
+		 @return				Interpolated value from the curve at provided time.
+		 */
+		T evaluate(float time, bool loop = true);
 
 	private:
-		void findKeys(const AnimationInstanceData& animInstance, UINT32& leftKey, UINT32& rightKey);
+		/** 
+		 * Returns a pair of keys that can be used for interpolating to field the value at the provided time. This attempts
+		 * to find keys using the cache first, and if not possible falls back to a full search.
+		 *
+		 * @param[in]	time			Time for which to find the relevant keys from. It is expected to be clamped to a
+		 *								valid range within the curve.
+		 * @param[in]	animInstance	Animation instance data holding the time to evaluate the curve at, and any cached
+		 *								data from previous requests. Time is expected to be clamped to a valid range
+		 *								within the curve.
+		 * @param[out]	leftKey			Index of the key to interpolate from.
+		 * @param[out]	rightKey		Index of the key to interpolate to.
+		 */
+		void findKeys(float time, const AnimationInstanceData<T>& animInstance, UINT32& leftKey, UINT32& rightKey);
+
+		/** 
+		 * Returns a pair of keys that can be used for interpolating to field the value at the provided time. 
+		 *
+		 * @param[in]	time			Time for which to find the relevant keys from. It is expected to be clamped to a
+		 *								valid range within the curve.
+		 * @param[out]	leftKey			Index of the key to interpolate from.
+		 * @param[out]	rightKey		Index of the key to interpolate to.
+		 */
 		void findKeys(float time, UINT32& leftKey, UINT32& rightKey);
 
+		/** 
+		 * Evaluates a value at the cached curve. Caller must ensure the request time falls within the cached curve range.
+		 *
+		 * @param[in]	animInstance	Animation instance data holding the time to evaluate the curve at, and any cached
+		 *								data from previous requests.
+		 * @return						Interpolated value from the curve at provided time.
+		 */
+		T evaluateCache(const AnimationInstanceData<T>& animInstance);
+
 		static const UINT32 CACHE_LOOKAHEAD;
 
 		Vector<KeyFrame> mKeyframes;

+ 41 - 0
Source/BansheeCore/Include/BsAnimationInstance.h

@@ -0,0 +1,41 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsCorePrerequisites.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation-Internal
+	 *  @{
+	 */
+
+	/** 
+	 * Data used for evaluating an AnimationCurve. Holds cached information so that sequential evaluations can be sped up.
+	 * You should not use the same instance of this object for evaluating multiple different animation curves.
+	 */
+	template <class T>
+	struct AnimationInstanceData
+	{
+	public:
+		AnimationInstanceData()
+			: time(0.0f), cachedKey((UINT32)-1), cachedCurveStart(std::numeric_limits<float>::infinity())
+			, cachedCurveEnd(0.0f), cachedCubicCoefficients()
+		{ }
+
+		float time; /**< Time at which to evaluate the curve. */
+	private:
+		template <class U> friend class TAnimationCurve;
+
+		mutable UINT32 cachedKey; /**< Left-most key the curve was last evaluated at. -1 if no cached data. */
+		mutable float cachedCurveStart; /**< Time relative to the animation curve, at which the cached data starts. */
+		mutable float cachedCurveEnd; /**< Time relative to the animation curve, at which the cached data end. */
+		/** 
+		 * Coefficients of the cubic hermite curve, in order [t^3, t^2, t, 1]. Coefficients assume unnormalized @p t, with
+		 * length of @p cachedCurveEnd - @p cachedCurveStart.
+		 */
+		mutable T cachedCubicCoefficients[4]; 
+	};
+
+	/** @} */
+}

+ 4 - 1
Source/BansheeCore/Include/BsCorePrerequisites.h

@@ -124,6 +124,10 @@
   *	Interface for interacting with the render API (DirectX, OpenGL, etc.).
   */
 
+/** @defgroup Animation-Internal Animation
+ *	Animation clips, skeletal and blend shape animation, animation playback, blending and other features.
+ */
+
 /** @defgroup Renderer-Internal Renderer
   *	Abstract interface and helper functionality for rendering scene objects.
   */
@@ -356,7 +360,6 @@ namespace BansheeEngine
 	class AudioSource;
 	class AudioClipImportOptions;
 	class AnimationClip;
-	struct AnimationInstanceData;
 	// Asset import
 	class SpecificImporter;
 	class Importer;

+ 1 - 5
Source/BansheeCore/Source/BsAnimation.cpp

@@ -4,9 +4,5 @@
 
 namespace BansheeEngine
 {
-	AnimationInstanceData::AnimationInstanceData()
-		:time(0.0f), cachedKey((UINT32)-1)
-	{
-		
-	}
+
 }

+ 88 - 20
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -1,7 +1,6 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsAnimationCurve.h"
-#include "BsAnimation.h"
 #include "BsVector3.h"
 #include "BsQuaternion.h"
 #include "BsMath.h"
@@ -43,38 +42,106 @@ namespace BansheeEngine
 	}
 
 	template <class T>
-	T TAnimationCurve<T>::evaluate(const AnimationInstanceData& animInstance, bool loop)
+	T TAnimationCurve<T>::evaluate(const AnimationInstanceData<T>& animInstance, bool loop)
 	{
 		if (mKeyframes.size() == 0)
 			return T();
 
 		float time = animInstance.time;
 
-		// TODO - If within cache evaluate it
+		// Wrap time if looping
+		if(loop)
+		{
+			if (time < mStart)
+				time = time - std::floor(time / mLength) * mLength;
+			else if (time > mEnd)
+				time = time - std::floor(time / mLength) * mLength;
+		}
 
-		// Clamp to start or loop
+		// If time is within cache, evaluate it directly
+		if (time >= animInstance.cachedCurveStart && time < animInstance.cachedCurveEnd)
+			return evaluateCache(animInstance);
+
+		// Clamp to start, cache constant of the first key and return
 		if(time < mStart)
+		{
+			animInstance.cachedCurveStart = -std::numeric_limits<float>::infinity();
+			animInstance.cachedCurveEnd = mStart;
+			animInstance.cachedKey = 0;
+			animInstance.cachedCubicCoefficients[0] = 0.0f;
+			animInstance.cachedCubicCoefficients[1] = 0.0f;
+			animInstance.cachedCubicCoefficients[2] = 0.0f;
+			animInstance.cachedCubicCoefficients[3] = mKeyframes[0].value;
+
+			return mKeyframes[0].value;
+		}
+		
+		if(time > mEnd) // Clamp to end, cache constant of the final key and return
+		{
+			UINT32 lastKey = (UINT32)mKeyframes.size() - 1;
+
+			animInstance.cachedCurveStart = mEnd;
+			animInstance.cachedCurveEnd = std::numeric_limits<float>::infinity();
+			animInstance.cachedKey = lastKey;
+			animInstance.cachedCubicCoefficients[0] = 0.0f;
+			animInstance.cachedCubicCoefficients[1] = 0.0f;
+			animInstance.cachedCubicCoefficients[2] = 0.0f;
+			animInstance.cachedCubicCoefficients[3] = mKeyframes[lastKey].value;
+
+			return mKeyframes[lastKey].value;
+		}
+
+		// Since our value is not in cache, search for the valid pair of keys of interpolate
+		UINT32 leftKeyIdx;
+		UINT32 rightKeyIdx;
+
+		findKeys(time, animInstance, leftKeyIdx, rightKeyIdx);
+
+		// Calculate cubic hermite curve coefficients so we can store them in cache
+		const KeyFrame& leftKey = mKeyframes[leftKeyIdx];
+		const KeyFrame& rightKey = mKeyframes[rightKeyIdx];
+
+		float length = rightKey.time - leftKey.time;
+		
+		animInstance.cachedCurveStart = leftKey.time;
+		animInstance.cachedCurveEnd = rightKey.time;
+		Math::cubicHermiteCoefficients(leftKey.value, rightKey.value, leftKey.outTangent, rightKey.inTangent, length,
+			animInstance.cachedCubicCoefficients);
+		// TODO - Handle stepped curve - If tangents are infinite assume constant value from left key is used
+
+		T output = evaluateCache(animInstance);
+
+		return output;
+	}
+
+	template <class T>
+	T TAnimationCurve<T>::evaluate(float time, bool loop)
+	{
+		if (mKeyframes.size() == 0)
+			return T();
+
+		// Clamp to start or loop
+		if (time < mStart)
 		{
 			if (loop)
 				time = time - std::floor(time / mLength) * mLength;
-			else
-				time = mStart; // TODO - Cache cubic hermite spline coefficients
+			else // Clamping
+				time = mStart;
 		}
 
 		// Clamp to end or loop
-		if(time > mEnd)
+		if (time > mEnd)
 		{
-			// TODO - Cache cubic hermite spline coefficients
-			if(loop)
+			if (loop)
 				time = time - std::floor(time / mLength) * mLength;
-			else
-				time = mEnd; // TODO - Cache cubic hermite spline coefficients
+			else // Clamping
+				time = mEnd;
 		}
 
 		UINT32 leftKeyIdx;
 		UINT32 rightKeyIdx;
 
-		findKeys(animInstance, leftKeyIdx, rightKeyIdx);
+		findKeys(time, leftKeyIdx, rightKeyIdx);
 
 		// Evaluate curve as hermite cubic spline
 		const KeyFrame& leftKey = mKeyframes[leftKeyIdx];
@@ -98,20 +165,21 @@ namespace BansheeEngine
 			rightTangent = rightKey.inTangent * length;
 		}
 
-		T output = Math::cubicHermite(t, leftKey.value, rightKey.value, leftTangent, rightTangent);
-
-		// TODO - Handle stepped curve - If tangents are infinite assume constant value from left key is used
+		return Math::cubicHermite(t, leftKey.value, rightKey.value, leftTangent, rightTangent);
+	}
 
-		// TODO - Cache cubic hermite spline coefficients
+	template <class T>
+	T TAnimationCurve<T>::evaluateCache(const AnimationInstanceData<T>& animInstance)
+	{
+		float t = animInstance.time - animInstance.cachedCurveStart;
 
-		return output;
+		const T* coeffs = animInstance.cachedCubicCoefficients;
+		return t * (t * (t * coeffs[0] + coeffs[1]) + coeffs[2]) + coeffs[3];
 	}
 
 	template <class T>
-	void TAnimationCurve<T>::findKeys(const AnimationInstanceData& animInstance, UINT32& leftKey, UINT32& rightKey)
+	void TAnimationCurve<T>::findKeys(float time, const AnimationInstanceData<T>& animInstance, UINT32& leftKey, UINT32& rightKey)
 	{
-		float time = animInstance.time;
-
 		// Check nearby keys first if there is cached data
 		if (animInstance.cachedKey != (UINT32)-1)
 		{

+ 37 - 3
Source/BansheeUtility/Include/BsMath.h

@@ -618,7 +618,9 @@ namespace BansheeEngine
 		}
 
 		/**
-		 * Calculates coefficients needed for evaluating a cubic curve in Hermite form.
+		 * Calculates coefficients needed for evaluating a cubic curve in Hermite form. Assumes @p t has been normalized is
+		 * in range [0, 1]. Tangents must be scaled by the length of the curve (length is the maximum value of @p t before
+		 * it was normalized).
 		 *
 		 * @param[in]	pointA			Starting point (at t=0).
 		 * @param[in]	pointB			Ending point (at t=1).
@@ -630,8 +632,40 @@ namespace BansheeEngine
 		static void cubicHermiteCoefficients(const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, 
 			T (&coefficients)[4])
 		{
-			coefficients[0] = 2 * pointA - 2 * pointB + tangentA + tangentB;
-			coefficients[1] = -3 * pointA + 3 * pointB - 2 * tangentA - tangentB;
+			T diff = pointA - pointB;
+
+			coefficients[0] = 2 * diff + tangentA + tangentB;
+			coefficients[1] = -3 * diff - 2 * tangentA - tangentB;
+			coefficients[2] = tangentA;
+			coefficients[4] = pointA;
+		}
+
+		/**
+		 * Calculates coefficients needed for evaluating a cubic curve in Hermite form. Assumes @p t is in range 
+		 * [0, @p length]. Tangents must not be scaled by @p length.
+		 *
+		 * @param[in]	pointA			Starting point (at t=0).
+		 * @param[in]	pointB			Ending point (at t=length).
+		 * @param[in]	tangentA		Starting tangent (at t=0).
+		 * @param[in]	tangentB		Ending tangent (at t=length).
+		 * @param[in]	length			Maximum value the curve will be evaluated at.
+		 * @param[out]	coefficients	Four coefficients for the cubic curve, in order [t^3, t^2, t, 1].
+		 */
+		template<class T>
+		static void cubicHermiteCoefficients(const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, 
+			float length, T (&coefficients)[4])
+		{
+			float length2 = length * length;
+			float invLength2 = 1.0f / length2;
+			float invLength3 = 1.0f / (length2 * length);
+
+			T scaledTangentA = tangentA * length;
+			T scaledTangentB = tangentB * length;
+
+			T diff = pointA - pointB;
+
+			coefficients[0] = (2 * diff + scaledTangentA + scaledTangentB) * invLength3;
+			coefficients[1] = (-3 * diff - 2 * scaledTangentA - scaledTangentB) * invLength2;
 			coefficients[2] = tangentA;
 			coefficients[4] = pointA;
 		}