Browse Source

Skeletal and custom translation/rotation/scale animation curves can now be mapped to scene object's transform

BearishSun 9 years ago
parent
commit
b7883f7ab7

+ 58 - 14
Source/BansheeCore/Include/BsAnimation.h

@@ -102,6 +102,23 @@ namespace BansheeEngine
 		HAnimationClip botRightClip;
 	};
 
+	/** Contains a mapping between a scene object and an animation curve it is animated with. */
+	struct AnimatedSceneObject
+	{
+		HSceneObject so;
+		String curveName;
+	};
+
+	/** Contains information about a scene object that is animated by a specific animation curve. */
+	struct AnimatedSceneObjectInfo
+	{
+		UINT64 id;
+		INT32 boneIdx; /**< Bone from which to access the transform. If -1 then no bone mapping is present. */
+		INT32 layerIdx; /**< If no bone mapping, layer on which the animation containing the referenced curve is in. */
+		INT32 stateIdx; /**< If no bone mapping, animation state containing the referenced curve. */
+		AnimationCurveMapping curveIndices; /**< Indices of the curves used for the transform. */
+	};
+
 	/** Represents a copy of the Animation data for use specifically on the animation thread. */
 	struct AnimationProxy
 	{
@@ -115,26 +132,29 @@ namespace BansheeEngine
 		 * Rebuilds the internal proxy data according to the newly assigned skeleton and clips. This should be called
 		 * whenever the animation skeleton changes.
 		 *
-		 * @param[in]		skeleton	New skeleton to assign to the proxy.
-		 * @param[in, out]	clipInfos	Potentially new clip infos that will be used for rebuilding the proxy. Once the
-		 *								method completes clip info layout and state indices will be populated for 
-		 *								further use in the update*() methods.
+		 * @param[in]		skeleton		New skeleton to assign to the proxy.
+		 * @param[in, out]	clipInfos		Potentially new clip infos that will be used for rebuilding the proxy. Once the
+		 *									method completes clip info layout and state indices will be populated for 
+		 *									further use in the update*() methods.
+		 * @param[in]		sceneObjects	A list of scene objects that are influenced by specific animation curves.
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
-		void rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos);
+		void rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos, 
+			const Vector<AnimatedSceneObject>& sceneObjects);
 
 		/** 
 		 * Rebuilds the internal proxy data according to the newly clips. This should be called whenever clips are added
 		 * or removed, or clip layout indices change.
 		 *
-		 * @param[in, out]	clipInfos	New clip infos that will be used for rebuilding the proxy. Once the method completes
-		 *								clip info layout and state indices will be populated for further use in the
-		 *								update*() methods.
+		 * @param[in, out]	clipInfos		New clip infos that will be used for rebuilding the proxy. Once the method 
+		 *									completes clip info layout and state indices will be populated for further use 
+		 *									in the update*() methods.
+		 * @param[in]		sceneObjects	A list of scene objects that are influenced by specific animation curves.
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
-		void rebuild(Vector<AnimationClipInfo>& clipInfos);
+		void rebuild(Vector<AnimationClipInfo>& clipInfos, const Vector<AnimatedSceneObject>& sceneObjects);
 
 		/** 
 		 * Updates the proxy data with new information about the clips. Caller must guarantee that clip layout didn't 
@@ -159,9 +179,12 @@ namespace BansheeEngine
 		AnimationStateLayer* layers;
 		UINT32 numLayers;
 		SPtr<Skeleton> skeleton;
+		UINT32 numSceneObjects;
+		AnimatedSceneObjectInfo* sceneObjectInfos;
 
 		// Evaluation results
-		LocalSkeletonPose localPose;
+		LocalSkeletonPose skeletonPose;
+		LocalSkeletonPose sceneObjectPose;
 		float* genericCurveOutputs;
 	};
 
@@ -280,12 +303,17 @@ namespace BansheeEngine
 		void setState(const HAnimationClip& clip, AnimationClipState state);
 
 		/** 
-		 * Triggers any events between the last frame and current one. 
+		 * Ensures that any position/rotation/scale animation of a specific animation curve is transfered to the
+		 * the provided scene object. Also allow the opposite operation which can allow scene object transform changes
+		 * to manipulate object bones.
 		 *
-		 * @param[in]	lastFrameTime	Time of the last frame.
-		 * @param[in]	delta			Difference between the last and this frame.
+		 * @param[in]	curve	Name of the curve (bone) to connect the scene object with.
+		 * @param[in]	so		Scene object to influence by the curve modifications, and vice versa.
 		 */
-		void triggerEvents(float lastFrameTime, float delta);
+		void mapCurveToSceneObject(const String& curve, const HSceneObject& so);
+
+		/** Removes the curve <-> scene object mapping that was set via mapCurveToSceneObject(). */
+		void unmapSceneObject(const HSceneObject& so);
 
 		/** Creates a new empty Animation object. */
 		static SPtr<Animation> create();
@@ -306,6 +334,14 @@ namespace BansheeEngine
 
 		Animation();
 
+		/** 
+		 * Triggers any events between the last frame and current one. 
+		 *
+		 * @param[in]	lastFrameTime	Time of the last frame.
+		 * @param[in]	delta			Difference between the last and this frame.
+		 */
+		void triggerEvents(float lastFrameTime, float delta);
+
 		/** 
 		 * Updates the animation proxy object based on the currently set skeleton, playing clips and dirty flags. 
 		 *
@@ -313,6 +349,13 @@ namespace BansheeEngine
 		 */
 		void updateAnimProxy(float timeDelta);
 
+		/**
+		 * Applies any outputs stored in the animation proxy (as written by the animation thread), and uses them to update
+		 * the animation state on the simulation thread. Caller must ensure that the animation thread has finished
+		 * with the animation proxy.
+		 */
+		void updateFromProxy();
+
 		/** 
 		 * Registers a new animation in the specified layer, or returns an existing animation clip info if the animation is
 		 * already registered. If @p stopExisting is true any existing animations in the layer will be stopped. Layout
@@ -327,6 +370,7 @@ namespace BansheeEngine
 
 		SPtr<Skeleton> mSkeleton;
 		Vector<AnimationClipInfo> mClipInfos;
+		UnorderedMap<UINT64, AnimatedSceneObject> mSceneObjects;
 
 		// Animation thread only
 		SPtr<AnimationProxy> mAnimProxy;

+ 11 - 1
Source/BansheeCore/Include/BsAnimationClip.h

@@ -122,7 +122,7 @@ namespace BansheeEngine
 		void setEvents(const Vector<AnimationEvent>& events) { mEvents = events; }
 
 		/**
-		 * Maps skeleton bone names to animation clip names, and returns a set of indices that can be easily used for
+		 * Maps skeleton bone names to animation curve names, and returns a set of indices that can be easily used for
 		 * locating an animation curve based on the bone index.
 		 *
 		 * @param[in]	skeleton	Skeleton to create the mapping for.
@@ -132,6 +132,16 @@ namespace BansheeEngine
 		 */
 		void getBoneMapping(const Skeleton& skeleton, AnimationCurveMapping* mapping) const;
 
+		/** 
+		 * Attempts to find translation/rotation/scale curves with the specified name and fills the mapping structure with
+		 * their indices, which can then be used for quick lookup.
+		 *
+		 * @param[in]	name		Name of the curves to look up.
+		 * @param[out]	mapping		Triple containing the translation/rotation/scale indices of the found curves. Indices
+		 *							will be -1 for curves that haven't been found.
+		 */
+		void getCurveMapping(const String& name, AnimationCurveMapping& mapping) const;
+
 		/** 
 		 * Checks are the curves contained within the clip additive. Additive clips are intended to be added on top of
 		 * other clips.

+ 1 - 0
Source/BansheeCore/Include/BsSkeleton.h

@@ -40,6 +40,7 @@ namespace BansheeEngine
 	{
 		SPtr<AnimationCurves> curves; /**< All curves in the animation clip. */
 		AnimationCurveMapping* boneToCurveMapping; /**< Mapping of bone indices to curve indices for quick lookup .*/
+		AnimationCurveMapping* soToCurveMapping; /**< Mapping of scene object indices to curve indices for quick lookup. */
 
 		TCurveCache<Vector3>* positionCaches; /**< Cache used for evaluating position curves. */
 		TCurveCache<Quaternion>* rotationCaches; /**< Cache used for evaluating rotation curves. */

+ 133 - 30
Source/BansheeCore/Source/BsAnimation.cpp

@@ -4,6 +4,7 @@
 #include "BsAnimationManager.h"
 #include "BsAnimationClip.h"
 #include "BsAnimationUtility.h"
+#include "BsSceneObject.h"
 
 namespace BansheeEngine
 {
@@ -29,7 +30,7 @@ namespace BansheeEngine
 	}
 
 	AnimationProxy::AnimationProxy(UINT64 id)
-		:id(id), layers(nullptr), numLayers(0), genericCurveOutputs(nullptr)
+		:id(id), layers(nullptr), numLayers(0), numSceneObjects(0), sceneObjectInfos(nullptr), genericCurveOutputs(nullptr)
 	{ }
 
 	AnimationProxy::~AnimationProxy()
@@ -83,53 +84,48 @@ namespace BansheeEngine
 						state.boneToCurveMapping[k].~AnimationCurveMapping();
 				}
 
+				if(state.soToCurveMapping != nullptr)
+				{
+					for(UINT32 k = 0; k < numSceneObjects; k++)
+						state.soToCurveMapping[k].~AnimationCurveMapping();
+				}
+
 				state.~AnimationState();
 			}
 
 			layer.~AnimationStateLayer();
 		}
 
-		// All the memory is part of the same buffer, so we only need to free the first element
+		// All of the memory is part of the same buffer, so we only need to free the first element
 		bs_free(layers);
 		layers = nullptr;
 		genericCurveOutputs = nullptr;
+		sceneObjectInfos = nullptr;
 
 		numLayers = 0;
+		numSceneObjects = 0;
 	}
 
-	void AnimationProxy::rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos)
+	void AnimationProxy::rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos, 
+		const Vector<AnimatedSceneObject>& sceneObjects)
 	{
 		this->skeleton = skeleton;
 
-		// Note: I could avoid having a separate allocation for LocalSkeletonPose and use the same buffer as the rest
+		// Note: I could avoid having a separate allocation for LocalSkeletonPoses and use the same buffer as the rest
 		// of AnimationProxy
 		if (skeleton != nullptr)
-			localPose = LocalSkeletonPose(skeleton->getNumBones());
-		else
-		{
-			UINT32 numPosCurves = 0;
-			UINT32 numRotCurves = 0;
-			UINT32 numScaleCurves = 0;
-
-			// Note: I'm recalculating this both here and in follow-up rebuild() call, it could be avoided.
-			for (auto& clipInfo : clipInfos)
-			{
-				if (!clipInfo.clip.isLoaded())
-					continue;
-
-				SPtr<AnimationCurves> curves = clipInfo.clip->getCurves();
-				numPosCurves += (UINT32)curves->position.size();
-				numRotCurves += (UINT32)curves->rotation.size();
-				numScaleCurves += (UINT32)curves->scale.size();
-			}
+			skeletonPose = LocalSkeletonPose(skeleton->getNumBones());
 
-			localPose = LocalSkeletonPose(numPosCurves, numRotCurves, numScaleCurves);
-		}
+		numSceneObjects = (UINT32)sceneObjects.size();
+		if (numSceneObjects > 0)
+			sceneObjectPose = LocalSkeletonPose(numSceneObjects);
+		else
+			sceneObjectPose = LocalSkeletonPose();
 
-		rebuild(clipInfos);
+		rebuild(clipInfos, sceneObjects);
 	}
 
-	void AnimationProxy::rebuild(Vector<AnimationClipInfo>& clipInfos)
+	void AnimationProxy::rebuild(Vector<AnimationClipInfo>& clipInfos, const Vector<AnimatedSceneObject>& sceneObjects)
 	{
 		clear();
 
@@ -201,9 +197,10 @@ namespace BansheeEngine
 			UINT32 scaleCacheSize = numScaleCurves * sizeof(TCurveCache<Vector3>);
 			UINT32 genCacheSize = numGenCurves * sizeof(TCurveCache<float>);
 			UINT32 genericCurveOutputSize = numGenCurves * sizeof(float);
+			UINT32 sceneObjectIdsSize = numSceneObjects * sizeof(AnimatedSceneObjectInfo);
 
 			UINT8* data = (UINT8*)bs_alloc(layersSize + clipsSize + boneMappingSize + posCacheSize + rotCacheSize + 
-				scaleCacheSize + genCacheSize + genericCurveOutputSize);
+				scaleCacheSize + genCacheSize + genericCurveOutputSize + sceneObjectIdsSize);
 
 			layers = (AnimationStateLayer*)data;
 			memcpy(layers, tempLayers.data(), layersSize);
@@ -246,6 +243,10 @@ namespace BansheeEngine
 			data += genCacheSize;
 
 			genericCurveOutputs = (float*)data;
+			data += genericCurveOutputSize;
+
+			sceneObjectInfos = (AnimatedSceneObjectInfo*)data;
+			data += sceneObjectIdsSize;
 
 			UINT32 curLayerIdx = 0;
 			UINT32 curStateIdx = 0;
@@ -347,7 +348,48 @@ namespace BansheeEngine
 
 				// Must be larger than zero otherwise the layer.states pointer will point to data held by some other layer
 				assert(layer.numStates > 0);
-			}			
+			}
+
+			for(UINT32 i = 0; i < numSceneObjects; i++)
+			{
+				AnimatedSceneObjectInfo& soInfo = sceneObjectInfos[i];
+				soInfo.id = sceneObjects[i].so.getInstanceId();
+				soInfo.boneIdx = -1;
+
+				// Check if the scene object maps to a bone (in which case we the system can just re-use the skeleton pose)
+				if (skeleton != nullptr)
+				{
+					for(UINT32 j = 0; j < numBones; j++)
+					{
+						if(skeleton->getBoneInfo(j).name == sceneObjects[i].curveName)
+						{
+							soInfo.boneIdx = j;
+							break;
+						}
+					}
+				}
+
+				// If no bone mapping, find curves directly
+				if(soInfo.boneIdx == -1)
+				{
+					soInfo.curveIndices = { (UINT32)-1, (UINT32)-1, (UINT32)-1 };
+
+					for(auto& clipInfo : clipInfos)
+					{
+						soInfo.layerIdx = clipInfo.layerIdx;
+						soInfo.stateIdx = clipInfo.stateIdx;
+
+						bool isClipValid = clipInfo.clip.isLoaded();
+						if (isClipValid)
+						{
+							// Note: If there are multiple clips with the relevant curve name, we only use the first
+
+							clipInfo.clip->getCurveMapping(sceneObjects[i].curveName, soInfo.curveIndices);
+							break;
+						}
+					}
+				}
+			}
 		}
 		bs_frame_clear();
 	}
@@ -800,6 +842,21 @@ namespace BansheeEngine
 		}
 	}
 
+	void Animation::mapCurveToSceneObject(const String& curve, const HSceneObject& so)
+	{
+		AnimatedSceneObject animSo = { so, curve };
+		mSceneObjects[so.getInstanceId()] = animSo;
+
+		mDirty |= AnimDirtyStateFlag::Skeleton;
+	}
+
+	void Animation::unmapSceneObject(const HSceneObject& so)
+	{
+		mSceneObjects.erase(so.getInstanceId());
+
+		mDirty |= AnimDirtyStateFlag::Skeleton;
+	}
+
 	SPtr<Animation> Animation::create()
 	{
 		Animation* anim = new (bs_alloc<Animation>()) Animation();
@@ -833,13 +890,59 @@ namespace BansheeEngine
 		else
 		{
 			if (mDirty.isSet(AnimDirtyStateFlag::Skeleton))
-				mAnimProxy->rebuild(mSkeleton, mClipInfos);
+			{
+				Vector<AnimatedSceneObject> animatedSO(mSceneObjects.size());
+				UINT32 idx = 0;
+				for(auto& entry : mSceneObjects)
+					animatedSO[idx++] = entry.second;
+
+				mAnimProxy->rebuild(mSkeleton, mClipInfos, animatedSO);
+			}
 			else if (mDirty.isSet(AnimDirtyStateFlag::Layout))
-				mAnimProxy->rebuild(mClipInfos);
+			{
+				Vector<AnimatedSceneObject> animatedSO(mSceneObjects.size());
+				UINT32 idx = 0;
+				for (auto& entry : mSceneObjects)
+					animatedSO[idx++] = entry.second;
+
+				mAnimProxy->rebuild(mClipInfos, animatedSO);
+			}
 			else if (mDirty.isSet(AnimDirtyStateFlag::Value))
 				mAnimProxy->updateValues(mClipInfos);
 		}
 
 		mDirty = AnimDirtyState();
 	}
+
+	void Animation::updateFromProxy()
+	{
+		// Write TRS animation results to relevant SceneObjects
+		for(UINT32 i = 0; i < mAnimProxy->numSceneObjects; i++)
+		{
+			const AnimatedSceneObjectInfo& soInfo = mAnimProxy->sceneObjectInfos[i];
+
+			auto iterFind = mSceneObjects.find(soInfo.id);
+			if (iterFind == mSceneObjects.end())
+				continue;
+
+			HSceneObject so = iterFind->second.so;
+			if (so.isDestroyed())
+				continue;
+
+			if(soInfo.boneIdx != -1)
+			{
+				so->setPosition(mAnimProxy->skeletonPose.positions[soInfo.boneIdx]);
+				so->setRotation(mAnimProxy->skeletonPose.rotations[soInfo.boneIdx]);
+				so->setScale(mAnimProxy->skeletonPose.scales[soInfo.boneIdx]);
+			}
+			else
+			{
+				so->setPosition(mAnimProxy->sceneObjectPose.positions[i]);
+				so->setRotation(mAnimProxy->sceneObjectPose.rotations[i]);
+				so->setScale(mAnimProxy->sceneObjectPose.scales[i]);
+			}
+		}
+
+		// TODO - Copy generic animation data and add a method for retrieving it
+	}
 }

+ 15 - 10
Source/BansheeCore/Source/BsAnimationClip.cpp

@@ -195,18 +195,23 @@ namespace BansheeEngine
 		{
 			const SkeletonBoneInfo& boneInfo = skeleton.getBoneInfo(i);
 
-			auto iterFind = mNameMapping.find(boneInfo.name);
-			if(iterFind != mNameMapping.end())
-			{
-				const UINT32* indices = iterFind->second;
+			getCurveMapping(boneInfo.name, mapping[i]);
+		}
+	}
 
-				mapping[i].position = indices[(UINT32)CurveType::Position];
-				mapping[i].rotation = indices[(UINT32)CurveType::Rotation];
-				mapping[i].scale = indices[(UINT32)CurveType::Scale];
-			}
-			else
-				mapping[i] = { (UINT32)-1, (UINT32)-1, (UINT32)-1 };
+	void AnimationClip::getCurveMapping(const String& name, AnimationCurveMapping& mapping) const
+	{
+		auto iterFind = mNameMapping.find(name);
+		if (iterFind != mNameMapping.end())
+		{
+			const UINT32* indices = iterFind->second;
+
+			mapping.position = indices[(UINT32)CurveType::Position];
+			mapping.rotation = indices[(UINT32)CurveType::Rotation];
+			mapping.scale = indices[(UINT32)CurveType::Scale];
 		}
+		else
+			mapping = { (UINT32)-1, (UINT32)-1, (UINT32)-1 };
 	}
 
 	RTTITypeBase* AnimationClip::getRTTIStatic()

+ 37 - 27
Source/BansheeCore/Source/BsAnimationManager.cpp

@@ -41,10 +41,10 @@ namespace BansheeEngine
 
 		// Trigger events
 		for (auto& anim : mAnimations)
+		{
+			anim.second->updateFromProxy();
 			anim.second->triggerEvents(mAnimationTime, gTime().getFrameDelta());
-
-		// TODO - Write TRS animation results to relevant SceneObjects
-		// TODO - Transfer generic curve evaluated data back to Animation
+		}
 
 		mWorkerRunning = false;
 	}
@@ -118,44 +118,54 @@ namespace BansheeEngine
 				info.numBones = numBones;
 
 				Matrix4* boneDst = renderData.transforms.data() + curBoneIdx;
-				anim->skeleton->getPose(boneDst, anim->localPose, anim->layers, anim->numLayers);
+				anim->skeleton->getPose(boneDst, anim->skeletonPose, anim->layers, anim->numLayers);
 
 				renderData.poseInfos[anim->id] = info;
 				curBoneIdx += numBones;
 			}
-			else
+
+			// Update mapped scene objects
+			for(UINT32 i = 0; i < anim->numSceneObjects; i++)
 			{
-				// Note: No blending for non-skeletal animations, just use first animation
-				if(anim->numLayers > 0 && anim->layers[0].numStates > 0)
-				{
-					const AnimationState& state = anim->layers[0].states[0];
+				const AnimatedSceneObjectInfo& soInfo = anim->sceneObjectInfos[i];
+
+				// We already evaluated bones
+				if (soInfo.boneIdx != -1)
+					continue;
 
+				const AnimationState& state = anim->layers[soInfo.layerIdx].states[soInfo.stateIdx];
+
+				{
+					UINT32 curveIdx = soInfo.curveIndices.position;
+					if (curveIdx != (UINT32)-1)
 					{
-						UINT32 numCurves = (UINT32)state.curves->position.size();
-						for(UINT32 i = 0; i < numCurves; i++)
-						{
-							const TAnimationCurve<Vector3>& curve = state.curves->position[i].curve;
-							anim->localPose.positions[i] = curve.evaluate(state.time, state.positionCaches[i], state.loop);
-						}
+						const TAnimationCurve<Vector3>& curve = state.curves->position[curveIdx].curve;
+						anim->sceneObjectPose.positions[curveIdx] = curve.evaluate(state.time, state.positionCaches[curveIdx], state.loop);
 					}
+					else
+						anim->sceneObjectPose.positions[curveIdx] = Vector3::ZERO;
+				}
 
+				{
+					UINT32 curveIdx = soInfo.curveIndices.rotation;
+					if (curveIdx != (UINT32)-1)
 					{
-						UINT32 numCurves = (UINT32)state.curves->rotation.size();
-						for (UINT32 i = 0; i < numCurves; i++)
-						{
-							const TAnimationCurve<Quaternion>& curve = state.curves->rotation[i].curve;
-							anim->localPose.rotations[i] = curve.evaluate(state.time, state.rotationCaches[i], state.loop);
-						}
+						const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve;
+						anim->sceneObjectPose.rotations[curveIdx] = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop);
 					}
+					else
+						anim->sceneObjectPose.rotations[curveIdx] = Quaternion::ZERO;
+				}
 
+				{
+					UINT32 curveIdx = soInfo.curveIndices.scale;
+					if (curveIdx != (UINT32)-1)
 					{
-						UINT32 numCurves = (UINT32)state.curves->scale.size();
-						for (UINT32 i = 0; i < numCurves; i++)
-						{
-							const TAnimationCurve<Vector3>& curve = state.curves->scale[i].curve;
-							anim->localPose.scales[i] = curve.evaluate(state.time, state.scaleCaches[i], state.loop);
-						}
+						const TAnimationCurve<Vector3>& curve = state.curves->scale[curveIdx].curve;
+						anim->sceneObjectPose.scales[curveIdx] = curve.evaluate(state.time, state.scaleCaches[curveIdx], state.loop);
 					}
+					else
+						anim->sceneObjectPose.scales[curveIdx] = Vector3::ONE;
 				}
 			}