Browse Source

Animation curve splitting (WIP)
Refactored skeletal pose evaluation

BearishSun 9 năm trước cách đây
mục cha
commit
27d5b94da0

+ 25 - 3
Source/BansheeCore/Include/BsAnimationCurve.h

@@ -47,7 +47,7 @@ namespace BansheeEngine
 		 *								curve value will be clamped.
 		 * @return						Interpolated value from the curve at provided time.
 		 */
-		T evaluate(const TCurveEvaluator<T>& animInstance, bool loop = true) const;
+		T evaluate(const TCurveEvaluatorData<T>& animInstance, bool loop = true) const;
 
 		/**
 		 * Evaluate the animation curve at the specified time. If evaluating multiple values in a sequential order consider
@@ -60,6 +60,15 @@ namespace BansheeEngine
 		 */
 		T evaluate(float time, bool loop = true) const;
 
+		/** 
+		 * Splits a piece of the animation curve into a separate animation curve. 
+		 *
+		 * @param[in]	start	Beginning time of the split curve.
+		 * @param[in]	end		End time of the split curve.
+		 * @return				New curve with data corresponding to the provided split times.
+		 */
+		TAnimationCurve<T> split(float start, float end);
+
 	private:
 		friend struct RTTIPlainType<TAnimationCurve<T>>;
 
@@ -75,7 +84,7 @@ namespace BansheeEngine
 		 * @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 TCurveEvaluator<T>& animInstance, UINT32& leftKey, UINT32& rightKey) const;
+		void findKeys(float time, const TCurveEvaluatorData<T>& animInstance, UINT32& leftKey, UINT32& rightKey) const;
 
 		/** 
 		 * Returns a pair of keys that can be used for interpolating to field the value at the provided time. 
@@ -87,6 +96,19 @@ namespace BansheeEngine
 		 */
 		void findKeys(float time, UINT32& leftKey, UINT32& rightKey) const;
 
+		/** Returns a keyframe index nearest to the provided time. */
+		UINT32 findKey(float time);
+
+		/** 
+		 * Calculates a key in-between the provided two keys. 
+		 *
+		 * @param[in]	lhs		Key to interpolate from.
+		 * @param[in]	rhs		Key to interpolate to.
+		 * @param[in]	t		Curve time to interpolate the keys at.
+		 * @return				Interpolated key value.
+		 */
+		KeyFrame evaluateKey(const KeyFrame& lhs, const KeyFrame& rhs, float time);
+
 		/** 
 		 * Evaluates a value at the cached curve. Caller must ensure the request time falls within the cached curve range.
 		 *
@@ -94,7 +116,7 @@ namespace BansheeEngine
 		 *								data from previous requests.
 		 * @return						Interpolated value from the curve at provided time.
 		 */
-		T evaluateCache(const TCurveEvaluator<T>& animInstance) const;
+		T evaluateCache(const TCurveEvaluatorData<T>& animInstance) const;
 
 		static const UINT32 CACHE_LOOKAHEAD;
 

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

@@ -519,7 +519,8 @@ namespace BansheeEngine
 		TID_KeyFrame = 1117,
 		TID_NamedAnimationCurve = 1118,
 		TID_Skeleton = 1119,
-		TID_SkeletonBoneInfo = 1120
+		TID_SkeletonBoneInfo = 1120,
+		TID_AnimationSplitInfo = 1121
 	};
 }
 

+ 2 - 2
Source/BansheeCore/Include/BsCurveEvaluator.h

@@ -15,10 +15,10 @@ namespace BansheeEngine
 	 * You should not use the same instance of this object for evaluating multiple different animation curves.
 	 */
 	template <class T>
-	struct TCurveEvaluator
+	struct TCurveEvaluatorData
 	{
 	public:
-		TCurveEvaluator()
+		TCurveEvaluatorData()
 			: time(0.0f), cachedKey((UINT32)-1), cachedCurveStart(std::numeric_limits<float>::infinity())
 			, cachedCurveEnd(0.0f), cachedCubicCoefficients()
 		{ }

+ 1 - 1
Source/BansheeCore/Include/BsImportOptions.h

@@ -26,7 +26,7 @@ namespace BansheeEngine
 	public:
 		friend class ImportOptionsRTTI;
 		static RTTITypeBase* getRTTIStatic();
-		virtual RTTITypeBase* getRTTI() const override;
+		RTTITypeBase* getRTTI() const override;
 	};
 
 	/** @} */

+ 41 - 7
Source/BansheeCore/Include/BsMeshImportOptions.h

@@ -19,6 +19,23 @@ namespace BansheeEngine
 		Convex /**< A convex hull will be generated from the source mesh. */
 	};
 
+	/** Contains information about a piece of imported animation that will be used for generating its own AnimationClip. */
+	struct AnimationSplitInfo : IReflectable
+	{
+		String name;
+		UINT32 startFrame;
+		UINT32 endFrame;
+		bool isAdditive;
+
+		/************************************************************************/
+		/* 								SERIALIZATION                      		*/
+		/************************************************************************/
+	public:
+		friend class AnimationSplitInfoRTTI;
+		static RTTITypeBase* getRTTIStatic();
+		RTTITypeBase* getRTTI() const override;
+	};
+
 	/**
 	 * Contains import options you may use to control how is a mesh imported from some external format into engine format.
 	 */
@@ -81,13 +98,21 @@ namespace BansheeEngine
 		/**	Retrieves a value that controls what type (if any) of collision mesh should be imported. */
 		CollisionMeshType getCollisionMeshType() const { return mCollisionMeshType; }
 
-		/************************************************************************/
-		/* 								SERIALIZATION                      		*/
-		/************************************************************************/
-	public:
-		friend class MeshImportOptionsRTTI;
-		static RTTITypeBase* getRTTIStatic();
-		virtual RTTITypeBase* getRTTI() const override;
+		/** 
+		 * Registers an animation split info that determines how will the source animation clip be split. If not splits
+		 * are present the data will be imported as one clip, but if splits are present the data will be split according
+		 * to the split infos.
+		 */
+		void addAnimationClipSplit(const AnimationSplitInfo& splitInfo) { mAnimationSplits.push_back(splitInfo); }
+
+		/** Returns in how many pieces should the imported animation clip be split info. */
+		UINT32 getNumAnimationClipSplits() const { return (UINT32)mAnimationSplits.size(); }
+
+		/** Returns information about an animation split at the specified index. */
+		const AnimationSplitInfo& getAnimationClipSplit(UINT32 idx) const { return mAnimationSplits[idx]; }
+
+		/** Removes an animation split info at the specified index. */
+		void removeAnimationClipSplit(UINT32 idx) { mAnimationSplits.erase(mAnimationSplits.begin() + idx); }
 
 	private:
 		bool mCPUReadable;
@@ -98,6 +123,15 @@ namespace BansheeEngine
 		bool mImportAnimation;
 		float mImportScale;
 		CollisionMeshType mCollisionMeshType;
+		Vector<AnimationSplitInfo> mAnimationSplits;
+
+		/************************************************************************/
+		/* 								SERIALIZATION                      		*/
+		/************************************************************************/
+	public:
+		friend class MeshImportOptionsRTTI;
+		static RTTITypeBase* getRTTIStatic();
+		RTTITypeBase* getRTTI() const override;
 	};
 
 	/** @} */

+ 32 - 0
Source/BansheeCore/Include/BsMeshImportOptionsRTTI.h

@@ -25,6 +25,7 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_PLAIN(mImportAnimation, 5)
 			BS_RTTI_MEMBER_PLAIN(mImportScale, 6)
 			BS_RTTI_MEMBER_PLAIN(mCollisionMeshType, 7)
+			BS_RTTI_MEMBER_REFL_ARRAY(mAnimationSplits, 8)
 		BS_END_RTTI_MEMBERS
 	public:
 		MeshImportOptionsRTTI()
@@ -48,6 +49,37 @@ namespace BansheeEngine
 		}
 	};
 
+	class BS_CORE_EXPORT AnimationSplitInfoRTTI : public RTTIType <AnimationSplitInfo, IReflectable, AnimationSplitInfoRTTI>
+	{
+	private:
+		BS_BEGIN_RTTI_MEMBERS
+			BS_RTTI_MEMBER_PLAIN(name, 0)
+			BS_RTTI_MEMBER_PLAIN(startFrame, 1)
+			BS_RTTI_MEMBER_PLAIN(endFrame, 2)
+			BS_RTTI_MEMBER_PLAIN(isAdditive, 3)
+		BS_END_RTTI_MEMBERS
+	public:
+		AnimationSplitInfoRTTI()
+			:mInitMembers(this)
+		{ }
+
+		const String& getRTTIName() override
+		{
+			static String name = "AnimationSplitInfo";
+			return name;
+		}
+
+		UINT32 getRTTIId() override
+		{
+			return TID_AnimationSplitInfo;
+		}
+
+		SPtr<IReflectable> newRTTIObject() override
+		{
+			return bs_shared_ptr_new<AnimationSplitInfo>();
+		}
+	};
+
 	/** @} */
 	/** @endcond */
 }

+ 14 - 8
Source/BansheeCore/Include/BsSkeleton.h

@@ -31,18 +31,26 @@ namespace BansheeEngine
 		Matrix4 invBindPose;
 	};
 
-	struct ANIM_BLEND_STATE_DESC
+	struct AnimationState
 	{
 		SPtr<AnimationCurves> curves;
 		Vector<AnimationCurveMapping> boneToCurveMapping;
 
-		TCurveEvaluator<Vector3> positionEval;
-		TCurveEvaluator<Quaternion> rotationEval;
-		TCurveEvaluator<Vector3> scaleEval;
+		TCurveEvaluatorData<Vector3> positionEval;
+		TCurveEvaluatorData<Quaternion> rotationEval;
+		TCurveEvaluatorData<Vector3> scaleEval;
 
 		float weight;
 		bool loop;
-		UINT8 layer;
+	};
+
+	struct AnimationStateLayer
+	{
+		AnimationState* states;
+		UINT32 numStates;
+
+		UINT8 index;
+		bool normalizeWeights;
 	};
 
 	struct SkeletonPose
@@ -69,9 +77,7 @@ namespace BansheeEngine
 		~Skeleton();
 
 		void getPose(SkeletonPose& pose, const AnimationClip& clip, float time, bool loop = true);
-
-		// Animations in same layer must be consecutive. Layers must be arranged in ascending order.
-		void getPose(SkeletonPose& pose, const ANIM_BLEND_STATE_DESC* states, UINT32 numStates);
+		void getPose(SkeletonPose& pose, const AnimationStateLayer* layers, UINT32 numLayers);
 
 		UINT32 getNumBones() const { return mNumBones; }
 		const SkeletonBoneInfo& getBoneInfo(UINT32 idx) const { return mBoneInfo[idx]; }

+ 75 - 3
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -50,7 +50,7 @@ namespace BansheeEngine
 	}
 
 	template <class T>
-	T TAnimationCurve<T>::evaluate(const TCurveEvaluator<T>& animInstance, bool loop) const
+	T TAnimationCurve<T>::evaluate(const TCurveEvaluatorData<T>& animInstance, bool loop) const
 	{
 		if (mKeyframes.size() == 0)
 			return T();
@@ -177,7 +177,7 @@ namespace BansheeEngine
 	}
 
 	template <class T>
-	T TAnimationCurve<T>::evaluateCache(const TCurveEvaluator<T>& animInstance) const
+	T TAnimationCurve<T>::evaluateCache(const TCurveEvaluatorData<T>& animInstance) const
 	{
 		float t = animInstance.time - animInstance.cachedCurveStart;
 
@@ -186,7 +186,7 @@ namespace BansheeEngine
 	}
 
 	template <class T>
-	void TAnimationCurve<T>::findKeys(float time, const TCurveEvaluator<T>& animInstance, UINT32& leftKey, UINT32& rightKey) const
+	void TAnimationCurve<T>::findKeys(float time, const TCurveEvaluatorData<T>& animInstance, UINT32& leftKey, UINT32& rightKey) const
 	{
 		// Check nearby keys first if there is cached data
 		if (animInstance.cachedKey != (UINT32)-1)
@@ -259,6 +259,78 @@ namespace BansheeEngine
 		rightKey = std::min(start, (INT32)mKeyframes.size() - 1);
 	}
 
+	template <class T>
+	UINT32 TAnimationCurve<T>::findKey(float time)
+	{
+		UINT32 leftKeyIdx;
+		UINT32 rightKeyIdx;
+
+		findKeys(time, leftKeyIdx, rightKeyIdx);
+
+		const KeyFrame& leftKey = mKeyframes[leftKeyIdx];
+		const KeyFrame& rightKey = mKeyframes[rightKeyIdx];
+
+		if (Math::abs(leftKey.time - time) <= Math::abs(rightKey.time - time))
+			return leftKeyIdx;
+		
+		return rightKeyIdx;
+	}
+
+	template <class T>
+	TKeyframe<T> TAnimationCurve<T>::evaluateKey(const KeyFrame& lhs, const KeyFrame& rhs, float time)
+	{
+		float length = rhs.time - lhs.time;
+		float t = (time - lhs.time) / length;
+
+		TKeyframe<T> output;
+		// TODO
+
+		return output;
+	}
+
+	template <class T>
+	TAnimationCurve<T> TAnimationCurve<T>::split(float start, float end)
+	{
+		Vector<TKeyframe<T>> keyFrames;
+
+		start = Math::clamp(start, mStart, mEnd);
+		end = Math::clamp(end, mStart, mEnd);
+
+		if (Math::approxEquals(end - start, 0.0f))
+			return TAnimationCurve<T>();
+
+		UINT32 startKeyIdx = findKey(start);
+		UINT32 endKeyIdx = findKey(end);
+
+		keyFrames.reserve(endKeyIdx - startKeyIdx + 2);
+
+		const KeyFrame& startKey = mKeyframes[startKeyIdx];
+		const KeyFrame& endKey = mKeyframes[endKeyIdx];
+
+		if(!Math::approxEquals(startKey.time, start))
+		{
+			keyFrames.push_back(evaluateKey(startKey, mKeyframes[startKeyIdx + 1], start));
+
+			if(start > startKey.time)
+				startKeyIdx++;
+		}
+
+		if(!Math::approxEquals(endKey.time, end))
+		{
+			keyFrames.push_back(evaluateKey(endKey, mKeyframes[endKeyIdx + 1], end));
+
+			if (end < endKey.time)
+				endKeyIdx--;
+		}
+
+		keyFrames.insert(keyFrames.begin(), mKeyframes.begin() + startKeyIdx, mKeyframes.begin() + endKeyIdx + 1);
+
+		for (auto& entry : keyFrames)
+			entry.time -= start;
+
+		return TAnimationCurve<T>(keyFrames);
+	}
+
 	template class TAnimationCurve<Vector3>;
 	template class TAnimationCurve<Quaternion>;
 	template class TAnimationCurve<float>;

+ 13 - 0
Source/BansheeCore/Source/BsMeshImportOptions.cpp

@@ -5,6 +5,19 @@
 
 namespace BansheeEngine
 {
+	/************************************************************************/
+	/* 								SERIALIZATION                      		*/
+	/************************************************************************/
+	RTTITypeBase* AnimationSplitInfo::getRTTIStatic()
+	{
+		return AnimationSplitInfoRTTI::instance();
+	}
+
+	RTTITypeBase* AnimationSplitInfo::getRTTI() const
+	{
+		return AnimationSplitInfo::getRTTIStatic();
+	}
+
 	MeshImportOptions::MeshImportOptions()
 		:mCPUReadable(false), mImportNormals(true), mImportTangents(true),
 		mImportBlendShapes(false), mImportSkin(false), mImportAnimation(false),

+ 45 - 54
Source/BansheeCore/Source/BsSkeleton.cpp

@@ -70,22 +70,27 @@ namespace BansheeEngine
 
 	void Skeleton::getPose(SkeletonPose& pose, const AnimationClip& clip, float time, bool loop)
 	{
-		ANIM_BLEND_STATE_DESC state;
+		AnimationState state;
 		state.curves = clip.getCurves();
-		state.layer = 0;
 		state.loop = loop;
 		state.weight = 1.0f;
 		state.positionEval.time = time;
 		state.rotationEval.time = time;
 		state.scaleEval.time = time;
 
+		AnimationStateLayer layer;
+		layer.index = 0;
+		layer.normalizeWeights = false;
+		layer.states = &state;
+		layer.numStates = 1;
+
 		state.boneToCurveMapping.resize(mNumBones);
 		clip.getBoneMapping(*this, state.boneToCurveMapping.data());
 
-		getPose(pose, &state, 1);
+		getPose(pose, &layer, 1);
 	}
 
-	void Skeleton::getPose(SkeletonPose& pose, const ANIM_BLEND_STATE_DESC* states, UINT32 numStates)
+	void Skeleton::getPose(SkeletonPose& pose, const AnimationStateLayer* layers, UINT32 numLayers)
 	{
 		assert(pose.numBones == mNumBones);
 
@@ -96,64 +101,50 @@ namespace BansheeEngine
 			pose.scales[i] = Vector3::ONE;
 		}
 
-		UINT32 stateIdx = 0;
-		UINT32 currentLayer = 0;
-		UINT32 numStatesInLayer = 0;
-		float layerWeight = 0.0f;
-
-		while(true)
+		for(UINT32 i = 0; i < numLayers; i++)
 		{
-			bool lastEntry = stateIdx == numStates;
-			if (lastEntry || currentLayer != states[stateIdx].layer)
+			const AnimationStateLayer& layer = layers[i];
+
+			float invLayerWeight;
+			if (layer.normalizeWeights)
 			{
-				if (!Math::approxEquals(layerWeight, 0.0f))
+				float weightSum = 0.0f;
+				for (UINT32 j = 0; j < layer.numStates; j++)
+					weightSum += layer.normalizeWeights;
+
+				invLayerWeight = 1.0f / weightSum;
+			}
+			else
+				invLayerWeight = 1.0f;
+
+
+			for (UINT32 j = 0; j < layer.numStates; j++)
+			{
+				const AnimationState& state = layer.states[i];
+
+				float normWeight = state.weight * invLayerWeight;
+				for (UINT32 k = 0; k < mNumBones; k++)
 				{
-					float invLayerWeight = 1.0f / layerWeight;
-					UINT32 start = stateIdx - numStatesInLayer;
+					const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];
 
-					for (UINT32 i = start; i < stateIdx; i++)
+					if (mapping.position != (UINT32)-1)
 					{
-						const ANIM_BLEND_STATE_DESC& state = states[i];
-
-						float normWeight = state.weight * invLayerWeight;
-						for (UINT32 j = 0; j < mNumBones; j++)
-						{
-							const AnimationCurveMapping& mapping = state.boneToCurveMapping[j];
-
-							if (mapping.position != (UINT32)-1)
-							{
-								const TAnimationCurve<Vector3>& curve = state.curves->position[mapping.position].curve;
-								pose.positions[j] += curve.evaluate(state.positionEval, state.loop) * normWeight;
-							}
-
-							if (mapping.rotation != (UINT32)-1)
-							{
-								const TAnimationCurve<Quaternion>& curve = state.curves->rotation[mapping.rotation].curve;
-								pose.rotations[j] += curve.evaluate(state.rotationEval, state.loop) * normWeight;
-							}
-
-							if (mapping.scale != (UINT32)-1)
-							{
-								const TAnimationCurve<Vector3>& curve = state.curves->scale[mapping.scale].curve;
-								pose.scales[j] += curve.evaluate(state.scaleEval, state.loop) * normWeight;
-							}
-						}
+						const TAnimationCurve<Vector3>& curve = state.curves->position[mapping.position].curve;
+						pose.positions[k] += curve.evaluate(state.positionEval, state.loop) * normWeight;
 					}
-				}
-
-				numStatesInLayer = 0;
-				layerWeight = 0.0f;
 
-				if (lastEntry)
-					break;
-			}
+					if (mapping.rotation != (UINT32)-1)
+					{
+						const TAnimationCurve<Quaternion>& curve = state.curves->rotation[mapping.rotation].curve;
+						pose.rotations[k] += curve.evaluate(state.rotationEval, state.loop) * normWeight;
+					}
 
-			{
-				const ANIM_BLEND_STATE_DESC& state = states[stateIdx];
-				currentLayer = state.layer;
-				layerWeight += state.weight;
-				numStatesInLayer++;
-				stateIdx++;
+					if (mapping.scale != (UINT32)-1)
+					{
+						const TAnimationCurve<Vector3>& curve = state.curves->scale[mapping.scale].curve;
+						pose.scales[k] += curve.evaluate(state.scaleEval, state.loop) * normWeight;
+					}
+				}
 			}
 		}