Browse Source

Animation playback (WIP)

BearishSun 9 years ago
parent
commit
76c14039e9

+ 1 - 1
Scripts/build_editor_win_x64.py

@@ -23,6 +23,6 @@ os.environ["PATH"] += os.pathsep + msbuildPath
 os.system("msbuild {0} /p:Configuration=OptimizedDebug;Platform=x64 /m".format(solutionPath))
 
 # Build Win32 game executable
-os.system("msbuild {0} /t:Game /p:Configuration=Release;Platform=x64;BuildProjectReferences=false /m".format(solutionPath))
+os.system("msbuild {0} /p:Configuration=Release;Platform=x64 /m".format(solutionPath))
 
 os.system("package_editor.py " + configuration)

+ 2 - 0
Source/BansheeCore/CMakeSources.cmake

@@ -502,6 +502,7 @@ set(BS_BANSHEECORE_INC_ANIMATION
 	"Include/BsAnimationClip.h"
 	"Include/BsSkeleton.h"
 	"Include/BsAnimation.h"
+	"Include/BsAnimationManager.h"
 	"Include/BsCurveEvaluator.h"
 )
 
@@ -510,6 +511,7 @@ set(BS_BANSHEECORE_SRC_ANIMATION
 	"Source/BsAnimationClip.cpp"
 	"Source/BsSkeleton.cpp"
 	"Source/BsAnimation.cpp"
+	"Source/BsAnimationManager.cpp"
 )
 
 source_group("Header Files\\Components" FILES ${BS_BANSHEECORE_INC_COMPONENTS})

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

@@ -3,12 +3,222 @@
 #pragma once
 
 #include "BsCorePrerequisites.h"
+#include "BsCoreObject.h"
+#include "BsFlags.h"
+#include "BsSkeleton.h"
 
 namespace BansheeEngine
 {
-	/** @addtogroup Animation
+	/** @addtogroup Animation-Internal
 	 *  @{
 	 */
 
+	/** Determines how an animation clip behaves when it reaches the end. */
+	enum class AnimWrapMode
+	{
+		Loop, /**< Loop around to the beginning/end when the last/first frame is reached. */
+		Clamp /**< Clamp to end/beginning, keeping the last/first frame active. */
+	};
+
+	/** Determines what happens to other animation clips when a new clip starts playing. */
+	enum class AnimPlayMode
+	{
+		StopAll, /**< All other animation clips are stopped. */
+		StopLayer /**< Only the clips within the current layer are stopped. */
+	};
+
+	/** Flags that determine which portion of Animation was changed and needs to be updated. */
+	enum class AnimDirtyStateFlag
+	{
+		Clean = 0,
+		Value = 1,
+		Layout = 2
+	};
+
+	typedef Flags<AnimDirtyStateFlag> AnimDirtyState;
+	BS_FLAGS_OPERATORS(AnimDirtyStateFlag)
+
+	/** Contains information about a currently playing animation clip. */
+	struct AnimationClipState
+	{
+		AnimationClipState() { }
+
+		/** Layer the clip is playing on. Multiple clips can be played simulatenously on different layers. */
+		UINT32 layer = 0; 
+		float time = 0.0f; /**< Current time the animation is playing from. */
+		float speed = 1.0f; /**< Speed at which the animation is playing. */
+		float weight = 1.0f; /**< Determines how much of an influence does the clip have on the final pose. */
+		/** Determines what happens to other animation clips when a new clip starts playing. */
+		AnimWrapMode wrapMode = AnimWrapMode::Loop;
+	};
+
+	/** Internal information about a single playing animation clip within Animation. */
+	struct PlayingClipInfo
+	{
+		PlayingClipInfo() { }
+		PlayingClipInfo(const HAnimationClip& clip);
+
+		HAnimationClip clip;
+		AnimationClipState state;
+	};
+
+	/** Represents a copy of the Animation data for use specifically on the animation thread. */
+	struct AnimationProxy
+	{
+		AnimationProxy();
+		~AnimationProxy();
+
+		/** 
+		 * Updates the proxy data with a new skeleton and clips. Very expensive update operation.
+		 *
+		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
+		 */
+		void updateSkeleton(const SPtr<Skeleton>& skeleton, const Vector<PlayingClipInfo>& clipInfos);
+
+		/** 
+		 * Updates the proxy data with new clips. Very expensive update operation.
+		 *
+		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
+		 */
+		void updateLayout(const Vector<PlayingClipInfo>& clipInfos);
+
+		/** 
+		 * Updates the proxy data with new information about the clips. Caller must guarantee that clip layout didn't 
+		 * change. Fairly cheap update operation.
+		 *
+		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
+		 */
+		void updateValues(const Vector<PlayingClipInfo>& clipInfos);
+
+		/** 
+		 * Updates the proxy data with new clip times. Caller must guarantee that clip layout didn't change. Very cheap
+		 * update operation.
+		 *
+		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
+		 */
+		void updateTime(const Vector<PlayingClipInfo>& clipInfos);
+
+		AnimationStateLayer* layers;
+		UINT32 numLayers;
+		SPtr<Skeleton> skeleton;
+		SkeletonPose pose;
+	};
+
+	/**
+	 * Handles animation playback. Takes one or multiple animation clips as input and evaluates them every animation update
+	 * tick depending on set properties. The evaluated data is used by the core thread for skeletal animation, by the sim
+	 * thread for updating attached scene objects and bones (if skeleton is attached), or the data is made available for
+	 * manual queries in the case of generic animation.
+	 */
+	class BS_CORE_EXPORT Animation : public CoreObject
+	{
+	public:
+		~Animation();
+
+		/** 
+		 * Changes the skeleton which will the translation/rotation/scale animation values manipulate. If no skeleton is set
+		 * the animation will only evaluate the generic curves, and the root translation/rotation/scale curves.
+		 */
+		void setSkeleton(const SPtr<Skeleton>& skeleton);
+
+		/** 
+		 * Changes the wrap mode for all active animations. Wrap mode determines what happens when animation reaches the 
+		 * first or last frame. 
+		 *
+		 * @see	AnimWrapMode
+		 */
+		void setWrapMode(AnimWrapMode wrapMode);
+
+		/** Changes the speed for all animations. The default value is 1.0f. Use negative values to play-back in reverse. */
+		void setSpeed(float speed);
+
+		/** 
+		 * Plays the specified animation clip at the specified layer. 
+		 *
+		 * @param[in]	clip		Clip to play.
+		 * @param[in]	layer		Layer to play the clip in. Multiple clips can be playing at once in separate layers.
+		 * @param[in]	playMode	Determines how are other playing animations handled when this animation starts.
+		 */
+		void play(const HAnimationClip& clip, UINT32 layer = 0, AnimPlayMode playMode = AnimPlayMode::StopLayer);
+
+		/**
+		 * Blends the specified animation clip with other animation clips playing in the specified layer.
+		 *
+		 * @param[in]	clip		Clip to blend.
+		 * @param[in]	weight		Determines how much of an effect will the blended animation have on the final output.
+		 *							In range [0, 1]. All animation weights on the same layer will be normalized.
+		 * @param[in]	fadeLength	Applies the blend over a specified time period, increasing the weight as the time
+		 *							passes. Set to zero to blend immediately. In seconds.
+		 * @param[in]	layer		Layer to play the clip in. Multiple clips can be playing at once in separate layers.
+		 */
+		void blend(const HAnimationClip& clip, float weight, float fadeLength = 0.0f, UINT32 layer = 0);
+
+		/**
+		 * Fades the specified animation clip in, while fading other playing animations out, over the specified time
+		 * period.
+		 *
+		 * @param[in]	clip		Clip to fade in.
+		 * @param[in]	fadeLength	Determines the time period over which the fade occurs. In seconds.
+		 * @param[in]	layer		Layer to play the clip in. Multiple clips can be playing at once in separate layers.
+		 * @param[in]	playMode	Determines should all other animations be faded out, or just the ones on the same layer.
+		 */
+		void crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer = 0, 
+			AnimPlayMode playMode = AnimPlayMode::StopLayer);
+
+		/** Stops playing all animations on the provided layer. */
+		void stop(UINT32 layer);
+
+		/** Stops playing all animations. */
+		void stopAll();
+		
+		/** Checks if any animation clips are currently playing. */
+		bool isPlaying() const;
+
+		/** 
+		 * Retrieves detailed information about a currently playing animation clip. 
+		 *
+		 * @param[in]	clip	Clip to retrieve the information for.
+		 * @param[out]	state	Animation clip state containing the requested information. Only valid if the method returns
+		 *						true.
+		 * @return				True if the state was found (animation clip is playing), false otherwise.
+		 */
+		bool getState(const HAnimationClip& clip, AnimationClipState& state);
+
+		/**
+		 * Changes the state of a playing animation clip. If animation clip is not currently playing the state change is
+		 * ignored.
+		 *
+		 * @param[in]	clip	Clip to change the state for.
+		 * @param[in]	state	New state of the animation (e.g. changing the time for seeking).
+		 */
+		void setState(const HAnimationClip& clip, AnimationClipState state);
+
+		/** Creates a new empty Animation object. */
+		static SPtr<Animation> create();
+
+	private:
+		friend class AnimationManager;
+
+		Animation();
+
+		/** 
+		 * Updates the animation proxy object based on the currently set skeleton, playing clips and dirty flags. 
+		 *
+		 * @param[in]	timeDelta	Seconds passed since the last call to this method.
+		 */
+		void updateAnimProxy(float timeDelta);
+
+		UINT64 mId;
+		AnimWrapMode mDefaultWrapMode;
+		float mDefaultSpeed;
+		AnimDirtyState mDirty;
+
+		SPtr<Skeleton> mSkeleton;
+		Vector<PlayingClipInfo> mPlayingClips;
+
+		// Animation thread only
+		AnimationProxy mAnimProxy;
+	};
+
 	/** @} */
 }

+ 39 - 0
Source/BansheeCore/Include/BsAnimationManager.h

@@ -0,0 +1,39 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsCorePrerequisites.h"
+#include "BsModule.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation-Internal
+	 *  @{
+	 */
+	
+	/** 
+	 * Keeps track of all active animations, queues animation thread tasks and synchronizes data between simulation, core
+	 * and animation threads.
+	 */
+	class AnimationManager : public Module<AnimationManager>
+	{
+	public:
+		AnimationManager();
+
+	private:
+		friend class Animation;
+
+		/** 
+		 * Registers a new animation and returns a unique ID for it. Must be called whenever an Animation is constructed. 
+		 */
+		UINT64 registerAnimation(Animation* anim);
+
+		/** Unregisters an animation with the specified ID. Must be called before an Animation is destroyed. */
+		void unregisterAnimation(UINT64 id);
+
+		UINT64 mNextId;
+		UnorderedMap<UINT64, Animation*> mAnimations;
+	};
+
+	/** @} */
+}

+ 79 - 23
Source/BansheeCore/Include/BsSkeleton.h

@@ -15,7 +15,10 @@ namespace BansheeEngine
 	 *  @{
 	 */
 
-	/** Contains indices for position/rotation/scale animation curves. */
+	/** 
+	 * Contains indices for position/rotation/scale animation curves. Used for quick mapping of bones in a skeleton to 
+	 * relevant animation curves. 
+	 */
 	struct AnimationCurveMapping
 	{
 		UINT32 position;
@@ -23,66 +26,119 @@ namespace BansheeEngine
 		UINT32 scale;
 	};
 
+	/** Information about a single bone used for constructing a skeleton. */
 	struct BONE_DESC
 	{
-		String name;
-		UINT32 parent;
+		String name; /**< Unique name of the bone. */
+		UINT32 parent; /**< Index of the parent bone, if any. -1 if root bone. */
 
-		Matrix4 invBindPose;
+		Matrix4 invBindPose; /**< Inverse bind pose which transforms vertices from their bind pose into local space. */
 	};
 
+	/** Contains information about a single playing animation clip. */
 	struct AnimationState
 	{
-		SPtr<AnimationCurves> curves;
-		Vector<AnimationCurveMapping> boneToCurveMapping;
+		SPtr<AnimationCurves> curves; /**< All curves in the animation clip. */
+		AnimationCurveMapping* boneToCurveMapping; /**< Mapping of bone indices to curve indices for quick lookup .*/
 
-		TCurveEvaluatorData<Vector3> positionEval;
-		TCurveEvaluatorData<Quaternion> rotationEval;
-		TCurveEvaluatorData<Vector3> scaleEval;
+		TCurveEvaluatorData<Vector3> positionEval; /**< Time value and cache used for evaluating position curves. */
+		TCurveEvaluatorData<Quaternion> rotationEval; /**< Time value and cache used for evaluating rotation curves. */
+		TCurveEvaluatorData<Vector3> scaleEval; /**< Time value and cache used for evaluating scale curves. */
 
-		float weight;
-		bool loop;
+		float weight; /**< Determines how much of an influence will this clip have in regard to others in the same layer. */
+		bool loop; /**< Determines should the animation loop (wrap) once ending or beginning frames are passed. */
 	};
 
+	/** Contains animation states for a single animation layer. */
 	struct AnimationStateLayer
 	{
-		AnimationState* states;
-		UINT32 numStates;
+		AnimationState* states; /**< Array of animation states in the layer. */
+		UINT32 numStates; /**< Number of states in @p states. */
+
+		UINT8 index; /**< Unique index of the animation layer. */
 
-		UINT8 index;
-		bool normalizeWeights;
+		/** 
+		 * Determines should weights of individual states be normalized or kept as is. Non-normalized weights allow the 
+		 * total contribution of all states to be less than one. 
+		 */
+		bool normalizeWeights; 
 	};
 
+	/** 
+	 * Contains information about translation, rotation and scale for every skeleton bone, after being evaluated at a
+	 * a specific time. All values are stored in the same order as the bones in the skeleton they were created by.
+	 */
 	struct SkeletonPose
 	{
+		SkeletonPose();
 		SkeletonPose(UINT32 numBones);
 		~SkeletonPose();
 
-		Matrix4* bonePoses; // Global bone poses
-		Vector3* positions; // Local positions
-		Quaternion* rotations; // Local rotations
-		Vector3* scales; // Local scales
-		UINT32 numBones;
+		/**< Global matrices transforming vectors from bind pose space to model space at specific animation time. */
+		Matrix4* bonePoses; 
+		Vector3* positions; /**< Local bone positions at specific animation time. */
+		Quaternion* rotations; /**< Local bone rotations at specific animation time. */
+		Vector3* scales; /**< Local bone scales at specific animation time. */
+		UINT32 numBones; /**< Number of bones in the pose. */
 	};
 
+	/** Contains internal information about a single bone in a Skeleton. */
 	struct SkeletonBoneInfo
 	{
-		String name;
-		UINT32 parent;
+		String name; /**< Unique name of the bone. */
+		UINT32 parent; /**< Index of the bone parent, or -1 if root (no parent). */
 	};
 
+	/** 
+	 * Contains information about bones required for skeletal animation. Allows caller to evaluate a set of animation
+	 * clips at a specific time and output the relevant skeleton pose.
+	 */
 	class BS_CORE_EXPORT Skeleton : public IReflectable // Note: Must be immutable in order to be usable on multiple threads
 	{
 	public:
 		~Skeleton();
 
+		/** 
+		 * Outputs a skeleton pose containing required transforms for transforming the skeleton to the values specified by
+		 * the provided animation clip evaluated at the specified time.
+		 *
+		 * @param[out]	pose	Output pose containing the requested transforms. Must be pre-allocated with enough space
+		 *						to hold all the bone data of this skeleton.
+		 * @param[in]	clip	Clip to evaluate.
+		 * @param[in]	time	Time to evaluate the clip with.
+		 * @param[in]	loop	Determines should the time be looped (wrapped) if it goes past the clip start/end.
+		 *
+		 * @note	It is more efficient to use the other getPose overload as sequential calls can benefit from animation
+		 *			evaluator cache.
+		 */
 		void getPose(SkeletonPose& pose, const AnimationClip& clip, float time, bool loop = true);
+
+		/** 
+		 * Outputs a skeleton pose containing required transforms for transforming the skeleton to the values specified by
+		 * the provided set of animation curves.
+		 *
+		 * @param[out]	pose		Output pose containing the requested transforms. Must be pre-allocated with enough space
+		 *							to hold all the bone data of this skeleton.
+		 * @param[in]	layers		One or multiple layers, containing one or multiple animation states to evaluate.
+		 * @param[in]	numLayers	Number of layers in the @p layers array.
+		 */
 		void getPose(SkeletonPose& pose, const AnimationStateLayer* layers, UINT32 numLayers);
 
+		/** Returns the total number of bones in the skeleton. */
 		UINT32 getNumBones() const { return mNumBones; }
+
+		/** Returns information about a bone at the provided index. */
 		const SkeletonBoneInfo& getBoneInfo(UINT32 idx) const { return mBoneInfo[idx]; }
-		const Matrix4& getBindPose(UINT32 idx) const { return mInvBindPoses[idx]; }
 
+		/** Returns the inverse bind pose for the bone at the provided index. */
+		const Matrix4& getInvBindPose(UINT32 idx) const { return mInvBindPoses[idx]; }
+
+		/** 
+		 * Creates a new Skeleton. 
+		 *
+		 * @param[in]	bones		An array of bones to initialize the skeleton with. Data will be copied.
+		 * @param[in]	numBones	Number of bones in the @p bones array.
+		 */
 		static SPtr<Skeleton> create(BONE_DESC* bones, UINT32 numBones);
 
 	private:

+ 284 - 0
Source/BansheeCore/Source/BsAnimation.cpp

@@ -1,8 +1,292 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsAnimation.h"
+#include "BsAnimationManager.h"
+#include "BsAnimationClip.h"
 
 namespace BansheeEngine
 {
+	PlayingClipInfo::PlayingClipInfo(const HAnimationClip& clip)
+		:clip(clip)
+	{ }
 
+	AnimationProxy::AnimationProxy()
+		:layers(nullptr), numLayers(0)
+	{ }
+
+	AnimationProxy::~AnimationProxy()
+	{
+		if (layers != nullptr)
+			bs_free(layers);
+	}
+
+	void AnimationProxy::updateSkeleton(const SPtr<Skeleton>& skeleton, const Vector<PlayingClipInfo>& clipInfos)
+	{
+		this->skeleton = skeleton;
+		pose = SkeletonPose(skeleton->getNumBones());
+
+		updateLayout(clipInfos);
+	}
+
+	void AnimationProxy::updateLayout(const Vector<PlayingClipInfo>& clipInfos)
+	{
+		if (layers != nullptr)
+			bs_free(layers);
+		
+		bs_frame_mark();
+		{
+			FrameVector<AnimationStateLayer> tempLayers;
+			for (auto& clipInfo : clipInfos)
+			{
+				auto iterFind = std::find_if(tempLayers.begin(), tempLayers.end(), 
+					[&](auto& x)
+				{
+					return x.index == clipInfo.state.layer;
+				});
+
+				if (iterFind == tempLayers.end())
+				{
+					tempLayers.push_back(AnimationStateLayer());
+					AnimationStateLayer& newLayer = tempLayers.back();
+
+					newLayer.index = clipInfo.state.layer;
+					newLayer.normalizeWeights = false; // TODO - This needs to be deduced from PlayingClipInfo
+				}
+			}
+
+			std::sort(tempLayers.begin(), tempLayers.end(), 
+				[&](auto& x, auto& y)
+			{
+				return x.index < y.index;
+			});
+
+			UINT32 numLayers = (UINT32)tempLayers.size();
+			UINT32 numClips = (UINT32)clipInfos.size();
+			UINT32 numBones = skeleton->getNumBones();
+
+			UINT32 layersSize = sizeof(AnimationStateLayer) * numLayers;
+			UINT32 clipsSize = sizeof(AnimationState) * numClips;
+			UINT32 boneMappingSize = numBones * numClips * sizeof(AnimationCurveMapping);
+			UINT8* data = (UINT8*)bs_alloc(layersSize + clipsSize + boneMappingSize);
+
+			layers = (AnimationStateLayer*)data;
+			memcpy(layers, tempLayers.data(), layersSize);
+			data += layersSize;
+
+			AnimationState* states = (AnimationState*)data;
+			data += clipsSize;
+
+			AnimationCurveMapping* boneMappings = (AnimationCurveMapping*)data;
+			UINT32 curStateIdx = 0;
+
+			for(auto& layer : tempLayers)
+			{
+				layer.states = &states[curStateIdx];
+				layer.numStates = 0;
+
+				for(auto& clipInfo : clipInfos)
+				{
+					if (clipInfo.state.layer != layer.index)
+						continue;
+
+					new (&states[curStateIdx]) AnimationState();
+					AnimationState& state = states[curStateIdx];
+					state.curves = clipInfo.clip->getCurves();
+					state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
+					state.weight = clipInfo.state.weight;
+					state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
+
+					state.positionEval.time = clipInfo.state.time;
+					state.rotationEval.time = clipInfo.state.time;
+					state.scaleEval.time = clipInfo.state.time;
+
+					clipInfo.clip->getBoneMapping(*skeleton, state.boneToCurveMapping);
+
+					layer.numStates++;
+					curStateIdx++;
+				}
+
+				// Must be larger than zero otherwise the layer.states pointer will point to data held by some other layer
+				assert(layer.numStates > 0);
+			}			
+		}
+		bs_frame_clear();
+	}
+
+	void AnimationProxy::updateValues(const Vector<PlayingClipInfo>& clipInfos)
+	{
+		// TODO
+	}
+
+	void AnimationProxy::updateTime(const Vector<PlayingClipInfo>& clipInfos)
+	{
+		// TODO
+	}
+
+	Animation::Animation()
+		:mDefaultWrapMode(AnimWrapMode::Loop), mDefaultSpeed(1.0f), mDirty(AnimDirtyStateFlag::Clean)
+	{
+		mId = AnimationManager::instance().registerAnimation(this);
+	}
+
+	Animation::~Animation()
+	{
+		AnimationManager::instance().unregisterAnimation(mId);
+	}
+
+	void Animation::setSkeleton(const SPtr<Skeleton>& skeleton)
+	{
+		mSkeleton = skeleton;
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	void Animation::setWrapMode(AnimWrapMode wrapMode)
+	{
+		mDefaultWrapMode = wrapMode;
+
+		for (auto& clipInfo : mPlayingClips)
+			clipInfo.state.wrapMode = wrapMode;
+
+		mDirty |= AnimDirtyStateFlag::Value;
+	}
+	void Animation::setSpeed(float speed)
+	{
+		mDefaultSpeed = speed;
+
+		for (auto& clipInfo : mPlayingClips)
+			clipInfo.state.speed = speed;
+
+		mDirty |= AnimDirtyStateFlag::Value;
+	}
+
+	void Animation::play(const HAnimationClip& clip, UINT32 layer, AnimPlayMode playMode)
+	{
+		if (playMode == AnimPlayMode::StopAll)
+			mPlayingClips.clear();
+		else
+		{
+			bs_frame_mark();
+			{
+				FrameVector<PlayingClipInfo> newClips;
+				for (auto& clipInfo : mPlayingClips)
+				{
+					if (clipInfo.state.layer != layer && clipInfo.clip != clip)
+						newClips.push_back(clipInfo);
+				}
+
+				mPlayingClips.resize(newClips.size());
+				memcpy(mPlayingClips.data(), newClips.data(), sizeof(PlayingClipInfo) * newClips.size());
+			}
+			bs_frame_clear();
+		}
+
+		mPlayingClips.push_back(PlayingClipInfo(clip));
+		PlayingClipInfo& newClipInfo = mPlayingClips.back();
+
+		newClipInfo.state.layer = layer;
+		
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	void Animation::blend(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
+	{
+		// TODO
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	void Animation::crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer, AnimPlayMode playMode)
+	{
+		// TODO
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	void Animation::stop(UINT32 layer)
+	{
+		bs_frame_mark();
+		{
+			FrameVector<PlayingClipInfo> newClips;
+			for (auto& clipInfo : mPlayingClips)
+			{
+				if (clipInfo.state.layer != layer)
+					newClips.push_back(clipInfo);
+			}
+
+			mPlayingClips.resize(newClips.size());
+			memcpy(mPlayingClips.data(), newClips.data(), sizeof(PlayingClipInfo) * newClips.size());
+		}
+		bs_frame_clear();
+
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	void Animation::stopAll()
+	{
+		mPlayingClips.clear();
+		mDirty |= AnimDirtyStateFlag::Layout;
+	}
+
+	bool Animation::isPlaying() const
+	{
+		return !mPlayingClips.empty();
+	}
+
+	bool Animation::getState(const HAnimationClip& clip, AnimationClipState& state)
+	{
+		for (auto& clipInfo : mPlayingClips)
+		{
+			if (clipInfo.clip == clip)
+			{
+				state = clipInfo.state;
+				return true;
+			}
+		}
+		
+		return false;
+	}
+
+	void Animation::setState(const HAnimationClip& clip, AnimationClipState state)
+	{
+		for (auto& clipInfo : mPlayingClips)
+		{
+			if (clipInfo.clip == clip)
+			{
+				if (clipInfo.state.layer != state.layer)
+					mDirty |= AnimDirtyStateFlag::Layout;
+
+				clipInfo.state = state;
+				mDirty |= AnimDirtyStateFlag::Value;
+				return;
+			}
+		}
+	}
+
+	SPtr<Animation> Animation::create()
+	{
+		Animation* anim = new (bs_alloc<Animation>()) Animation();
+
+		SPtr<Animation> animPtr = bs_core_ptr(anim);
+		animPtr->_setThisPtr(animPtr);
+		animPtr->initialize();
+
+		return animPtr;
+	}
+
+	void Animation::updateAnimProxy(float timeDelta)
+	{
+		// TODO - Ensure that animation task is not running (also perhaps another memory barrier before start?)
+		
+		// TODO - Skeleton might not be available but I'm assuming it will be in the Proxy (and possibly at other places)
+		//   - In order to allow TRS animation without a skeleton, always provide a single-bone skeleton?
+
+		// TODO - Check if any of the clip curves are dirty
+
+		// TODO - If layout dirty, do full rebuild
+		//      - If value dirty, update all values
+		//      - If nothing dirty, just advance the times
+
+		// TODO - Make sure anim manager performs a memory barrier after these calls
+	}
+
+	
+	// TODO - When non-looping clips reach the end make sure to remove them from the playing clips list
 }

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

@@ -0,0 +1,23 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsAnimationManager.h"
+
+namespace BansheeEngine
+{
+	AnimationManager::AnimationManager()
+		:mNextId(1)
+	{
+
+	}
+
+	UINT64 AnimationManager::registerAnimation(Animation* anim)
+	{
+		mAnimations[mNextId] = anim;
+		return mNextId++;
+	}
+
+	void AnimationManager::unregisterAnimation(UINT64 animId)
+	{
+		mAnimations.erase(animId);
+	}
+}

+ 10 - 3
Source/BansheeCore/Source/BsSkeleton.cpp

@@ -6,6 +6,10 @@
 
 namespace BansheeEngine
 {
+	SkeletonPose::SkeletonPose()
+		: bonePoses(nullptr), positions(nullptr), rotations(nullptr), scales(nullptr), numBones(0)
+	{ }
+
 	SkeletonPose::SkeletonPose(UINT32 numBones)
 		: numBones(numBones)
 	{
@@ -34,7 +38,8 @@ namespace BansheeEngine
 
 	SkeletonPose::~SkeletonPose()
 	{
-		bs_free(bonePoses);
+		if(bonePoses != nullptr)
+			bs_free(bonePoses);
 	}
 
 	Skeleton::Skeleton()
@@ -70,8 +75,11 @@ namespace BansheeEngine
 
 	void Skeleton::getPose(SkeletonPose& pose, const AnimationClip& clip, float time, bool loop)
 	{
+		Vector<AnimationCurveMapping> boneToCurveMapping(mNumBones);
+
 		AnimationState state;
 		state.curves = clip.getCurves();
+		state.boneToCurveMapping = boneToCurveMapping.data();
 		state.loop = loop;
 		state.weight = 1.0f;
 		state.positionEval.time = time;
@@ -84,8 +92,7 @@ namespace BansheeEngine
 		layer.states = &state;
 		layer.numStates = 1;
 
-		state.boneToCurveMapping.resize(mNumBones);
-		clip.getBoneMapping(*this, state.boneToCurveMapping.data());
+		clip.getBoneMapping(*this, state.boneToCurveMapping);
 
 		getPose(pose, &layer, 1);
 	}