Просмотр исходного кода

Better animation blending interface
Implemented animation fade out/in

BearishSun 9 лет назад
Родитель
Сommit
3b6b7fa3be

+ 95 - 24
Source/BansheeCore/Include/BsAnimation.h

@@ -6,6 +6,7 @@
 #include "BsCoreObject.h"
 #include "BsCoreObject.h"
 #include "BsFlags.h"
 #include "BsFlags.h"
 #include "BsSkeleton.h"
 #include "BsSkeleton.h"
+#include "BsVector2.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -20,13 +21,6 @@ namespace BansheeEngine
 		Clamp /**< Clamp to end/beginning, keeping the last/first frame active. */
 		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. */
 	/** Flags that determine which portion of Animation was changed and needs to be updated. */
 	enum class AnimDirtyStateFlag
 	enum class AnimDirtyStateFlag
 	{
 	{
@@ -62,6 +56,10 @@ namespace BansheeEngine
 		HAnimationClip clip;
 		HAnimationClip clip;
 		AnimationClipState state;
 		AnimationClipState state;
 		
 		
+		float fadeDirection;
+		float fadeTime;
+		float fadeLength;
+
 		/** 
 		/** 
 		 * Version of the animation curves used by the AnimationProxy. Used to detecting the internal animation curves
 		 * Version of the animation curves used by the AnimationProxy. Used to detecting the internal animation curves
 		 * changed. 
 		 * changed. 
@@ -71,6 +69,43 @@ namespace BansheeEngine
 		UINT32 stateIdx; /**< State index this clip belongs to in AnimationProxy structure. */
 		UINT32 stateIdx; /**< State index this clip belongs to in AnimationProxy structure. */
 	};
 	};
 
 
+	/** Defines a single animation clip in BlendSequentialInfo. */
+	struct BS_CORE_EXPORT BlendSequentialClipInfo
+	{
+		BlendSequentialClipInfo() { }
+
+		HAnimationClip clip;
+		float fadeTime = 0.0f;
+		float startTime = 0.0f;
+		float endTime = 0.0f;
+	};
+
+	/** Defines a sequential blend where one animation clip is played after another, with an optional fade between them. */
+	struct BS_CORE_EXPORT BlendSequentialInfo
+	{
+		BlendSequentialInfo(UINT32 numClips);
+		~BlendSequentialInfo();
+
+		BlendSequentialClipInfo* clips;
+		UINT32 numClips;
+	};
+
+	/** Defines a 1D blend where two animation clips are blended between each other using linear interpolation. */
+	struct Blend1DInfo
+	{
+		HAnimationClip leftClip;
+		HAnimationClip rightClip;
+	};
+
+	/** Defines a 2D blend where two animation clips are blended between each other using bilinear interpolation. */
+	struct Blend2DInfo
+	{
+		HAnimationClip topLeftClip;
+		HAnimationClip topRightClip;
+		HAnimationClip botLeftClip;
+		HAnimationClip botRightClip;
+	};
+
 	/** Represents a copy of the Animation data for use specifically on the animation thread. */
 	/** Represents a copy of the Animation data for use specifically on the animation thread. */
 	struct AnimationProxy
 	struct AnimationProxy
 	{
 	{
@@ -157,39 +192,71 @@ namespace BansheeEngine
 		void setSpeed(float speed);
 		void setSpeed(float speed);
 
 
 		/** 
 		/** 
-		 * Plays the specified animation clip at the specified layer. 
+		 * Plays the specified animation clip. 
 		 *
 		 *
 		 * @param[in]	clip		Clip to play.
 		 * @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);
+		void play(const HAnimationClip& clip);
 
 
 		/**
 		/**
-		 * Blends the specified animation clip with other animation clips playing in the specified layer.
+		 * Plays the specified animation clip on top of the animation currently playing in the main layer. Multiple
+		 * such clips can be playing at once, as long as you ensure each is given its own layer. Each animation can
+		 * also have a weight that determines how much it influences the main animation.
 		 *
 		 *
-		 * @param[in]	clip		Clip to blend.
+		 * @param[in]	clip		Clip to additively blend. Must contain additive animation curves.
 		 * @param[in]	weight		Determines how much of an effect will the blended animation have on the final output.
 		 * @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.
+		 *							In range [0, 1].
 		 * @param[in]	fadeLength	Applies the blend over a specified time period, increasing the weight as the time
 		 * @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.
 		 *							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.
+		 * @param[in]	layer		Layer to play the clip in. Multiple additive clips can be playing at once in separate
+		 *							layers and each layer has its own weight.
+		 */
+		void blendAdditive(const HAnimationClip& clip, float weight, float fadeLength = 0.0f, UINT32 layer = 0);
+
+		/**
+		 * Plays a set of animation clips sequentially one after another, with an optional fade between them.
+		 *
+		 * @param[in]	info		Describes all animation clips to play.
+		 */
+		void blendSequential(const BlendSequentialInfo& info);
+
+		/**
+		 * Blend two animation clips between each other using linear interpolation. Unlike normal animations these
+		 * animations are not advanced with the progress of time, and is instead expected the user manually changes the
+		 * @p t parameter.
+		 *
+		 * @param[in]	info	Information about the clips to blend.
+		 * @param[in]	t		Parameter that controls the blending, in range [0, 1]. t = 0 means left animation has full
+		 *						influence, t = 1 means right animation has full influence.
 		 */
 		 */
-		void blend(const HAnimationClip& clip, float weight, float fadeLength = 0.0f, UINT32 layer = 0);
+		void blend1D(const Blend1DInfo& info, float t);
 
 
 		/**
 		/**
-		 * Fades the specified animation clip in, while fading other playing animations out, over the specified time
+		 * Blend four animation clips between each other using bilinear interpolation. Unlike normal animations these
+		 * animations are not advanced with the progress of time, and is instead expected the user manually changes the
+		 * @p t parameter.
+		 *
+		 * @param[in]	info	Information about the clips to blend.
+		 * @param[in]	t		Parameter that controls the blending, in range [(0, 0), (1, 1)]. t = (0, 0) means top left
+		 *						animation has full influence, t = (0, 1) means top right animation has full influence,
+		 *						t = (1, 0) means bottom left animation has full influence, t = (1, 1) means bottom right
+		 *						animation has full influence.
+		 */
+		void blend2D(const Blend2DInfo& info, const Vector2& t);
+
+		/**
+		 * Fades the specified animation clip in, while fading other playing animation out, over the specified time
 		 * period.
 		 * period.
 		 *
 		 *
 		 * @param[in]	clip		Clip to fade in.
 		 * @param[in]	clip		Clip to fade in.
 		 * @param[in]	fadeLength	Determines the time period over which the fade occurs. In seconds.
 		 * @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);
+		void crossFade(const HAnimationClip& clip, float fadeLength);
 
 
-		/** Stops playing all animations on the provided layer. */
+		/** 
+		 * Stops playing all animations on the provided layer. Specify -1 to stop animation on the main layer 
+		 * (non-additive animations). 
+		 */
 		void stop(UINT32 layer);
 		void stop(UINT32 layer);
 
 
 		/** Stops playing all animations. */
 		/** Stops playing all animations. */
@@ -240,8 +307,12 @@ namespace BansheeEngine
 		 */
 		 */
 		void updateAnimProxy(float timeDelta);
 		void updateAnimProxy(float timeDelta);
 
 
-		/** Checks if all the clips in the provided layer are additive. If not it prints out a warning. */
-		void checkAdditiveLayer(UINT32 layerIdx);
+		/** 
+		 * 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
+		 * will be marked as dirty if any changes were made.
+		 */
+		AnimationClipInfo* addClip(const HAnimationClip& clip, UINT32 layer, bool stopExisting = true);
 
 
 		UINT64 mId;
 		UINT64 mId;
 		AnimWrapMode mDefaultWrapMode;
 		AnimWrapMode mDefaultWrapMode;

+ 245 - 90
Source/BansheeCore/Source/BsAnimation.cpp

@@ -7,13 +7,26 @@
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	AnimationClipInfo::AnimationClipInfo()
 	AnimationClipInfo::AnimationClipInfo()
-		:layerIdx(0), curveVersion(0), stateIdx(0)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), layerIdx(0), curveVersion(0), stateIdx(0)
 	{ }
 	{ }
 
 
 	AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
 	AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
-		:clip(clip), curveVersion(0), layerIdx(0), stateIdx(0)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx(0), stateIdx(0)
 	{ }
 	{ }
 
 
+	BlendSequentialInfo::BlendSequentialInfo(UINT32 numClips)
+		: clips(nullptr), numClips(numClips)
+	{
+		if (numClips > 0)
+			bs_newN<BlendSequentialClipInfo>(numClips);
+	}
+
+	BlendSequentialInfo::~BlendSequentialInfo()
+	{
+		if(clips != nullptr)
+			bs_deleteN(clips, numClips);
+	}
+
 	AnimationProxy::AnimationProxy(UINT64 id)
 	AnimationProxy::AnimationProxy(UINT64 id)
 		:id(id), layers(nullptr), numLayers(0), genericCurveOutputs(nullptr)
 		:id(id), layers(nullptr), numLayers(0), genericCurveOutputs(nullptr)
 	{ }
 	{ }
@@ -67,10 +80,16 @@ namespace BansheeEngine
 			FrameVector<AnimationStateLayer> tempLayers;
 			FrameVector<AnimationStateLayer> tempLayers;
 			for (auto& clipInfo : clipInfos)
 			for (auto& clipInfo : clipInfos)
 			{
 			{
+				UINT32 layer = clipInfo.state.layer;
+				if (layer == (UINT32)-1)
+					layer = 0;
+				else
+					layer += 1;
+
 				auto iterFind = std::find_if(tempLayers.begin(), tempLayers.end(), 
 				auto iterFind = std::find_if(tempLayers.begin(), tempLayers.end(), 
 					[&](auto& x)
 					[&](auto& x)
 				{
 				{
-					return x.index == clipInfo.state.layer;
+					return x.index == layer;
 				});
 				});
 
 
 				if (iterFind == tempLayers.end())
 				if (iterFind == tempLayers.end())
@@ -78,7 +97,7 @@ namespace BansheeEngine
 					tempLayers.push_back(AnimationStateLayer());
 					tempLayers.push_back(AnimationStateLayer());
 					AnimationStateLayer& newLayer = tempLayers.back();
 					AnimationStateLayer& newLayer = tempLayers.back();
 
 
-					newLayer.index = clipInfo.state.layer;
+					newLayer.index = layer;
 					newLayer.additive = clipInfo.clip.isLoaded() && clipInfo.clip->isAdditive();
 					newLayer.additive = clipInfo.clip.isLoaded() && clipInfo.clip->isAdditive();
 				}
 				}
 			}
 			}
@@ -167,10 +186,27 @@ namespace BansheeEngine
 
 
 					new (&states[curStateIdx]) AnimationState();
 					new (&states[curStateIdx]) AnimationState();
 					AnimationState& state = states[curStateIdx];
 					AnimationState& state = states[curStateIdx];
-					state.weight = clipInfo.state.weight;
 					state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
 					state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
 					state.time = clipInfo.state.time;
 					state.time = clipInfo.state.time;
 
 
+					// Calculate weight if fading is active
+					float weight = clipInfo.state.weight;
+
+					//// Assumes time is clamped to [0, fadeLength] and fadeLength != 0
+					if(clipInfo.fadeDirection < 0.0f)
+					{
+						float t = clipInfo.fadeTime / clipInfo.fadeLength;
+						weight *= (1.0f - t);
+					}
+					else if(clipInfo.fadeDirection > 0.0f)
+					{
+						float t = clipInfo.fadeTime / clipInfo.fadeLength;
+						weight *= t;
+					}
+
+					state.weight = weight;
+
+					// Set up individual curves and their caches
 					bool isClipValid = clipInfo.clip.isLoaded();
 					bool isClipValid = clipInfo.clip.isLoaded();
 					if(isClipValid)
 					if(isClipValid)
 						state.curves = clipInfo.clip->getCurves();
 						state.curves = clipInfo.clip->getCurves();
@@ -198,6 +234,7 @@ namespace BansheeEngine
 					if(isClipValid)
 					if(isClipValid)
 						clipInfo.curveVersion = clipInfo.clip->getVersion();
 						clipInfo.curveVersion = clipInfo.clip->getVersion();
 
 
+					// Set up bone mapping
 					if (skeleton != nullptr)
 					if (skeleton != nullptr)
 					{
 					{
 						state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
 						state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
@@ -285,77 +322,175 @@ namespace BansheeEngine
 		mDefaultSpeed = speed;
 		mDefaultSpeed = speed;
 
 
 		for (auto& clipInfo : mClipInfos)
 		for (auto& clipInfo : mClipInfos)
-			clipInfo.state.speed = speed;
+		{
+			// Special case: Ignore non-moving ones
+			if(clipInfo.state.speed != 0.0f)
+				clipInfo.state.speed = speed;
+		}
 
 
 		mDirty |= AnimDirtyStateFlag::Value;
 		mDirty |= AnimDirtyStateFlag::Value;
 	}
 	}
 
 
-	void Animation::play(const HAnimationClip& clip, UINT32 layer, AnimPlayMode playMode)
+	void Animation::play(const HAnimationClip& clip)
 	{
 	{
-		if (playMode == AnimPlayMode::StopAll)
-			mClipInfos.clear();
-		else
+		AnimationClipInfo* clipInfo = addClip(clip, (UINT32)-1);
+		if(clipInfo != nullptr)
 		{
 		{
-			bs_frame_mark();
-			{
-				FrameVector<AnimationClipInfo> newClips;
-				for (auto& clipInfo : mClipInfos)
-				{
-					if (clipInfo.state.layer != layer && clipInfo.clip != clip)
-						newClips.push_back(clipInfo);
-				}
+			clipInfo->state.time = 0.0f;
+			clipInfo->state.speed = mDefaultSpeed;
+			clipInfo->state.weight = 1.0f;
+			clipInfo->state.wrapMode = mDefaultWrapMode;
 
 
-				mClipInfos.resize(newClips.size());
-				memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
-			}
-			bs_frame_clear();
+			mDirty |= AnimDirtyStateFlag::Value;
 		}
 		}
+	}
 
 
-		if (clip != nullptr)
+	void Animation::blendAdditive(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
+	{
+		if(clip != nullptr && !clip->isAdditive())
 		{
 		{
-			checkAdditiveLayer(layer);
+			LOGWRN("blendAdditive() called with a clip that doesn't contain additive animation. Ignoring.");
 
 
-			mClipInfos.push_back(AnimationClipInfo(clip));
-			AnimationClipInfo& newClipInfo = mClipInfos.back();
+			// Stop any clips on this layer, even if invalid
+			HAnimationClip nullClip;
+			addClip(nullClip, layer);
 
 
-			newClipInfo.state.layer = layer;
+			return;
 		}
 		}
 
 
-		mDirty |= AnimDirtyStateFlag::Layout;
+		AnimationClipInfo* clipInfo = addClip(clip, layer);
+		if (clipInfo != nullptr)
+		{
+			clipInfo->state.time = 0.0f;
+			clipInfo->state.speed = mDefaultSpeed;
+			clipInfo->state.weight = weight;
+			clipInfo->state.wrapMode = mDefaultWrapMode;
+
+			if(fadeLength > 0.0f)
+			{
+				clipInfo->fadeDirection = 1.0f;
+				clipInfo->fadeTime = 0.0f;
+				clipInfo->fadeLength = fadeLength;
+			}
+
+			mDirty |= AnimDirtyStateFlag::Value;
+		}
 	}
 	}
 
 
-	void Animation::blend(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
+	void Animation::blend1D(const Blend1DInfo& info, float t)
 	{
 	{
-		if (clip != nullptr)
+		AnimationClipInfo* leftClipInfo = addClip(info.leftClip, (UINT32)-1, true);
+		if (leftClipInfo != nullptr)
 		{
 		{
-			checkAdditiveLayer(layer);
+			leftClipInfo->state.time = 0.0f;
+			leftClipInfo->state.speed = 0.0f;
+			leftClipInfo->state.weight = 1.0f - t;
+			leftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+		}
 
 
-			mClipInfos.push_back(AnimationClipInfo(clip));
-			AnimationClipInfo& newClipInfo = mClipInfos.back();
+		AnimationClipInfo* rightClipInfo = addClip(info.rightClip, (UINT32)-1, false);
+		if(rightClipInfo != nullptr)
+		{
+			rightClipInfo->state.time = 0.0f;
+			rightClipInfo->state.speed = 0.0f;
+			rightClipInfo->state.weight = t;
+			rightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+		}
+
+		mDirty |= AnimDirtyStateFlag::Value;
+	}
 
 
-			newClipInfo.state.layer = layer;
+	void Animation::blend2D(const Blend2DInfo& info, const Vector2& t)
+	{
+		AnimationClipInfo* topLeftClipInfo = addClip(info.topLeftClip, (UINT32)-1, true);
+		if (topLeftClipInfo != nullptr)
+		{
+			topLeftClipInfo->state.time = 0.0f;
+			topLeftClipInfo->state.speed = 0.0f;
+			topLeftClipInfo->state.weight = (1.0f - t.x) * (1.0f - t.y);
+			topLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
 		}
 		}
 
 
-		// TODO
+		AnimationClipInfo* topRightClipInfo = addClip(info.topRightClip, (UINT32)-1, false);
+		if (topRightClipInfo != nullptr)
+		{
+			topRightClipInfo->state.time = 0.0f;
+			topRightClipInfo->state.speed = 0.0f;
+			topRightClipInfo->state.weight = t.x * (1.0f - t.y);
+			topRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+		}
 
 
-		mDirty |= AnimDirtyStateFlag::Layout;
+		AnimationClipInfo* botLeftClipInfo = addClip(info.botLeftClip, (UINT32)-1, false);
+		if (botLeftClipInfo != nullptr)
+		{
+			botLeftClipInfo->state.time = 0.0f;
+			botLeftClipInfo->state.speed = 0.0f;
+			botLeftClipInfo->state.weight = (1.0f - t.x) * t.y;
+			botLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+		}
+
+		AnimationClipInfo* botRightClipInfo = addClip(info.botRightClip, (UINT32)-1, false);
+		if (botRightClipInfo != nullptr)
+		{
+			botRightClipInfo->state.time = 0.0f;
+			botRightClipInfo->state.speed = 0.0f;
+			botRightClipInfo->state.weight = t.x * t.y;
+			botRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
+		}
+
+		mDirty |= AnimDirtyStateFlag::Value;
 	}
 	}
 
 
-	void Animation::crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer, AnimPlayMode playMode)
+	void Animation::crossFade(const HAnimationClip& clip, float fadeLength)
 	{
 	{
-		if (clip != nullptr)
+		bool isFading = fadeLength > 0.0f;
+		if(!isFading)
+		{
+			play(clip);
+			return;
+		}
+
+		AnimationClipInfo* clipInfo = addClip(clip, (UINT32)-1, false);
+		if (clipInfo != nullptr)
 		{
 		{
-			checkAdditiveLayer(layer);
+			clipInfo->state.time = 0.0f;
+			clipInfo->state.speed = mDefaultSpeed;
+			clipInfo->state.weight = 1.0f;
+			clipInfo->state.wrapMode = mDefaultWrapMode;
 
 
-			mClipInfos.push_back(AnimationClipInfo(clip));
-			AnimationClipInfo& newClipInfo = mClipInfos.back();
+			// Set up fade lengths
+			clipInfo->fadeDirection = 1.0f;
+			clipInfo->fadeTime = 0.0f;
+			clipInfo->fadeLength = fadeLength;
 
 
-			newClipInfo.state.layer = layer;
+			for (auto& entry : mClipInfos)
+			{
+				if (entry.state.layer == (UINT32)-1 && entry.clip != clip)
+				{
+					// If other clips are already cross-fading, we need to persist their current weight before starting
+					// a new crossfade. We do that by adjusting the fade times.
+					if(clipInfo->fadeDirection != 0 && clipInfo->fadeTime < clipInfo->fadeLength)
+					{
+						float t = clipInfo->fadeTime / clipInfo->fadeLength;
+						if (clipInfo->fadeDirection < 0.0f)
+							t = (1.0f - t);
+
+						clipInfo->state.weight *= t;
+					}
+
+					clipInfo->fadeDirection = -1.0f;
+					clipInfo->fadeTime = 0.0f;
+					clipInfo->fadeLength = fadeLength;
+				}
+			}
+
+			mDirty |= AnimDirtyStateFlag::Value;
 		}
 		}
+	}
 
 
+	void Animation::blendSequential(const BlendSequentialInfo& info)
+	{
 		// TODO
 		// TODO
-
-		mDirty |= AnimDirtyStateFlag::Layout;
 	}
 	}
 
 
 	void Animation::stop(UINT32 layer)
 	void Animation::stop(UINT32 layer)
@@ -367,14 +502,14 @@ namespace BansheeEngine
 			{
 			{
 				if (clipInfo.state.layer != layer)
 				if (clipInfo.state.layer != layer)
 					newClips.push_back(clipInfo);
 					newClips.push_back(clipInfo);
+				else
+					mDirty |= AnimDirtyStateFlag::Layout;
 			}
 			}
 
 
 			mClipInfos.resize(newClips.size());
 			mClipInfos.resize(newClips.size());
 			memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
 			memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
 		}
 		}
 		bs_frame_clear();
 		bs_frame_clear();
-
-		mDirty |= AnimDirtyStateFlag::Layout;
 	}
 	}
 
 
 	void Animation::stopAll()
 	void Animation::stopAll()
@@ -383,6 +518,59 @@ namespace BansheeEngine
 		mDirty |= AnimDirtyStateFlag::Layout;
 		mDirty |= AnimDirtyStateFlag::Layout;
 	}
 	}
 
 
+	AnimationClipInfo* Animation::addClip(const HAnimationClip& clip, UINT32 layer, bool stopExisting)
+	{
+		AnimationClipInfo* output = nullptr;
+		bool hasExisting = false;
+
+		// Search for existing
+		for (auto& clipInfo : mClipInfos)
+		{
+			if (clipInfo.state.layer == layer)
+			{
+				if (clipInfo.clip == clip)
+					output = &clipInfo;
+				else if (stopExisting)
+					hasExisting = true;
+			}
+		}
+
+		// Doesn't exist or found extra animations, rebuild
+		if (output == nullptr || hasExisting)
+		{
+			bs_frame_mark();
+			{
+				FrameVector<AnimationClipInfo> newClips;
+				for (auto& clipInfo : mClipInfos)
+				{
+					if (!stopExisting || clipInfo.state.layer != layer || clipInfo.clip == clip)
+						newClips.push_back(clipInfo);
+				}
+
+				if (output == nullptr && clip != nullptr)
+					newClips.push_back(AnimationClipInfo());
+
+				mClipInfos.resize(newClips.size());
+				memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
+
+				mDirty |= AnimDirtyStateFlag::Layout;
+			}
+			bs_frame_clear();
+		}
+
+		// If new clip was added, get its address
+		if (output == nullptr && clip != nullptr)
+		{
+			AnimationClipInfo& newInfo = mClipInfos.back();
+			newInfo.clip = clip;
+			newInfo.layerIdx = layer;
+
+			output = &newInfo;
+		}
+		
+		return output;
+	}
+
 	bool Animation::isPlaying() const
 	bool Animation::isPlaying() const
 	{
 	{
 		for(auto& clipInfo : mClipInfos)
 		for(auto& clipInfo : mClipInfos)
@@ -413,50 +601,13 @@ namespace BansheeEngine
 
 
 	void Animation::setState(const HAnimationClip& clip, AnimationClipState state)
 	void Animation::setState(const HAnimationClip& clip, AnimationClipState state)
 	{
 	{
-		if (clip == nullptr)
-			return;
-
-		checkAdditiveLayer(state.layer);
-
-		for (auto& clipInfo : mClipInfos)
-		{
-			if (clipInfo.clip == clip)
-			{
-				if (clipInfo.state.layer != state.layer)
-					mDirty |= AnimDirtyStateFlag::Layout;
-
-				clipInfo.state = state;
-				mDirty |= AnimDirtyStateFlag::Value;
-				return;
-			}
-		}
-
-		// No existing clip found, add new one
-		mClipInfos.push_back(AnimationClipInfo(clip));
-
-		AnimationClipInfo& newClipInfo = mClipInfos.back();
-		newClipInfo.state = state;
+		AnimationClipInfo* clipInfo = addClip(clip, state.layer, false);
 
 
-		mDirty |= AnimDirtyStateFlag::Layout;
-	}
-
-	void Animation::checkAdditiveLayer(UINT32 layerIdx)
-	{
-		for (auto& clipInfo : mClipInfos)
-		{
-			if (!clipInfo.clip.isLoaded())
-				continue;
+		if (clipInfo == nullptr)
+			return;
 
 
-			if (clipInfo.state.layer == layerIdx)
-			{
-				if(!clipInfo.clip->isAdditive())
-				{
-					LOGWRN("Adding an additive clip to a layer, but other clips in the same layer are not additive. " \
-						"This will most likely result in an incorrect animation.");
-					return;
-				}
-			}
-		}
+		clipInfo->state = state;
+		mDirty |= AnimDirtyStateFlag::Value;
 	}
 	}
 
 
 	SPtr<Animation> Animation::create()
 	SPtr<Animation> Animation::create()
@@ -472,13 +623,17 @@ namespace BansheeEngine
 
 
 	void Animation::updateAnimProxy(float timeDelta)
 	void Animation::updateAnimProxy(float timeDelta)
 	{
 	{
-		// Check if any of the clip curves are dirty and advance time
+		// Check if any of the clip curves are dirty and advance time, perform fading
 		for (auto& clipInfo : mClipInfos)
 		for (auto& clipInfo : mClipInfos)
 		{
 		{
-			clipInfo.state.time += timeDelta * clipInfo.state.speed;
+			float scaledTimeDelta = timeDelta * clipInfo.state.speed;
+			clipInfo.state.time += scaledTimeDelta;
 
 
 			if (clipInfo.clip.isLoaded() && clipInfo.curveVersion != clipInfo.clip->getVersion())
 			if (clipInfo.clip.isLoaded() && clipInfo.curveVersion != clipInfo.clip->getVersion())
 				mDirty |= AnimDirtyStateFlag::Layout;
 				mDirty |= AnimDirtyStateFlag::Layout;
+
+			float fadeTime = clipInfo.fadeTime + scaledTimeDelta;
+			clipInfo.fadeTime = Math::clamp(fadeTime, 0.0f, clipInfo.fadeLength);
 		}
 		}
 
 
 		if((UINT32)mDirty == 0) // Clean
 		if((UINT32)mDirty == 0) // Clean

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

@@ -148,6 +148,11 @@ namespace BansheeEngine
 				const AnimationState& state = layer.states[j];
 				const AnimationState& state = layer.states[j];
 
 
 				float normWeight = state.weight * invLayerWeight;
 				float normWeight = state.weight * invLayerWeight;
+
+				// Early exit for clips that don't contribute (which there could be plenty especially for sequential blends)
+				if (Math::approxEquals(normWeight, 0.0f))
+					continue;
+
 				for (UINT32 k = 0; k < mNumBones; k++)
 				for (UINT32 k = 0; k < mNumBones; k++)
 				{
 				{
 					const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];
 					const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];

+ 13 - 5
Source/BansheeEngine/Include/BsCAnimation.h

@@ -37,14 +37,22 @@ namespace BansheeEngine
 		void setSpeed(float speed);
 		void setSpeed(float speed);
 
 
 		/** @copydoc Animation::play */
 		/** @copydoc Animation::play */
-		void play(const HAnimationClip& clip, UINT32 layer = 0, AnimPlayMode playMode = AnimPlayMode::StopLayer);
+		void play(const HAnimationClip& clip);
 
 
-		/** @copydoc Animation::blend */
-		void blend(const HAnimationClip& clip, float weight, float fadeLength = 0.0f, UINT32 layer = 0);
+		/** @copydoc Animation::blendAdditive */
+		void blendAdditive(const HAnimationClip& clip, float weight, float fadeLength = 0.0f, UINT32 layer = 0);
+
+		/** @copydoc Animation::blendSequential */
+		void blendSequential(const BlendSequentialInfo& info);
+
+		/** @copydoc Animation::blend1D */
+		void blend1D(const Blend1DInfo& info, float t);
+
+		/** @copydoc Animation::blend2D */
+		void blend2D(const Blend2DInfo& info, const Vector2& t);
 
 
 		/** @copydoc Animation::crossFade */
 		/** @copydoc Animation::crossFade */
-		void crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer = 0, 
-			AnimPlayMode playMode = AnimPlayMode::StopLayer);
+		void crossFade(const HAnimationClip& clip, float fadeLength);
 
 
 		/** @copydoc Animation::stop */
 		/** @copydoc Animation::stop */
 		void stop(UINT32 layer);
 		void stop(UINT32 layer);

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

@@ -41,22 +41,40 @@ namespace BansheeEngine
 			mInternal->setSpeed(speed);
 			mInternal->setSpeed(speed);
 	}
 	}
 
 
-	void CAnimation::play(const HAnimationClip& clip, UINT32 layer, AnimPlayMode playMode)
+	void CAnimation::play(const HAnimationClip& clip)
 	{
 	{
 		if (mInternal != nullptr)
 		if (mInternal != nullptr)
-			mInternal->play(clip, layer, playMode);
+			mInternal->play(clip);
+	}
+
+	void CAnimation::blendAdditive(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
+	{
+		if (mInternal != nullptr)
+			mInternal->play(clip);
+	}
+
+	void CAnimation::blendSequential(const BlendSequentialInfo& info)
+	{
+		if (mInternal != nullptr)
+			mInternal->blendSequential(info);
+	}
+
+	void CAnimation::blend1D(const Blend1DInfo& info, float t)
+	{
+		if (mInternal != nullptr)
+			mInternal->blend1D(info, t);
 	}
 	}
 
 
-	void CAnimation::blend(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
+	void CAnimation::blend2D(const Blend2DInfo& info, const Vector2& t)
 	{
 	{
 		if (mInternal != nullptr)
 		if (mInternal != nullptr)
-			mInternal->blend(clip, weight, fadeLength, layer);
+			mInternal->blend2D(info, t);
 	}
 	}
 
 
-	void CAnimation::crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer, AnimPlayMode playMode)
+	void CAnimation::crossFade(const HAnimationClip& clip, float fadeLength)
 	{
 	{
 		if (mInternal != nullptr)
 		if (mInternal != nullptr)
-			mInternal->crossFade(clip, fadeLength, layer, playMode);
+			mInternal->crossFade(clip, fadeLength);
 	}
 	}
 
 
 	void CAnimation::stop(UINT32 layer)
 	void CAnimation::stop(UINT32 layer)

+ 92 - 31
Source/MBansheeEngine/Animation/Animation.cs

@@ -25,18 +25,42 @@ namespace BansheeEngine
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Determines what happens to other animation clips when a new clip starts playing.
+    /// Defines a single animation clip in <see cref="BlendSequentialInfo"/>.
     /// </summary>
     /// </summary>
-    public enum AnimPlayMode // Note: Must match C++ enum AnimPlayMode
+    public class BlendSequentialClipInfo
     {
     {
-        /// <summary>
-        /// All other animation clips are stopped.
-        /// </summary>
-        StopAll,
-        /// <summary>
-        /// Only the clips within the current layer are stopped.
-        /// </summary>
-		StopLayer
+        public AnimationClip clip;
+        public float fadeTime = 0.0f;
+        public float startTime = 0.0f;
+        public float endTime = 0.0f;
+    }
+
+    /// <summary>
+    /// Defines a sequential blend where one animation clip is played after another, with an optional fade between them.
+    /// </summary>
+    public class BlendSequentialInfo
+    {
+        public BlendSequentialClipInfo[] clips;
+    }
+
+    /// <summary>
+    /// Defines a 1D blend where two animation clips are blended between each other using linear interpolation.
+    /// </summary>
+    public class Blend1DInfo
+    {
+        public AnimationClip leftClip;
+        public AnimationClip rightClip;
+    }
+
+    /// <summary>
+    /// Defines a 2D blend where two animation clips are blended between each other using bilinear interpolation.
+    /// </summary>
+    public class Blend2DInfo
+    {
+        public AnimationClip topLeftClip;
+        public AnimationClip topRightClip;
+        public AnimationClip botLeftClip;
+        public AnimationClip botRightClip;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -115,7 +139,7 @@ namespace BansheeEngine
                 serializableData.defaultClip = value;
                 serializableData.defaultClip = value;
 
 
                 if (value != null && _native != null)
                 if (value != null && _native != null)
-                    _native.Play(value, 0, AnimPlayMode.StopLayer);
+                    _native.Play(value);
             }
             }
         }
         }
 
 
@@ -166,46 +190,83 @@ namespace BansheeEngine
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Plays the specified animation clip at the specified layer.
+        /// Plays the specified animation clip. 
         /// </summary>
         /// </summary>
         /// <param name="clip">Clip to play.</param>
         /// <param name="clip">Clip to play.</param>
-        /// <param name="layer">Layer to play the clip in. Multiple clips can be playing at once in separate layers.</param>
-        /// <param name="playMode">Determines how are other playing animations handled when this animation starts.</param>
-        public void Play(AnimationClip clip, int layer = 0, AnimPlayMode playMode = AnimPlayMode.StopLayer)
+        public void Play(AnimationClip clip)
         {
         {
             if(_native != null)
             if(_native != null)
-                _native.Play(clip, layer, playMode);
+                _native.Play(clip);
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Blends the specified animation clip with other animation clips playing in the specified layer.
+        /// Plays the specified animation clip on top of the animation currently playing in the main layer. Multiple such
+        /// clips can be playing at once, as long as you ensure each is given its own layer. Each animation can also have a
+        /// weight that determines how much it influences the main animation.        
         /// </summary>
         /// </summary>
-        /// <param name="clip">Clip to blend.</param>
+        /// <param name="clip">Clip to additively blend. Must contain additive animation curves.</param>
         /// <param name="weight">Determines how much of an effect will the blended animation have on the final output.
         /// <param name="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 range [0, 1].</param>
         /// <param name="fadeLength">Applies the blend over a specified time period, increasing the weight as the time
         /// <param name="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>
         ///                          passes. Set to zero to blend immediately. In seconds.</param>
-        /// <param name="layer">Layer to play the clip in. Multiple clips can be playing at once in separate layers.</param>
-        public void Blend(AnimationClip clip, float weight = 1.0f, float fadeLength = 0.0f, int layer = 0)
+        /// <param name="layer">Layer to play the clip in. Multiple additive clips can be playing at once in separate layers
+        ///                     and each layer has its own weight.</param>
+        public void BlendAdditive(AnimationClip clip, float weight, float fadeLength, int layer)
+        {
+            if (_native != null)
+                _native.BlendAdditive(clip, weight, fadeLength, layer);
+        }
+
+        /// <summary>
+        /// Plays a set of animation clips sequentially one after another, with an optional fade between them.
+        /// </summary>
+        /// <param name="info">Describes all animation clips to play.</param>
+        public void BlendSequential(BlendSequentialInfo info)
+        {
+            if (_native != null)
+                _native.BlendSequential(info);
+        }
+
+        /// <summary>
+        /// Blend two animation clips between each other using linear interpolation. Unlike normal animations these
+        /// animations are not advanced with the progress of time, and is instead expected the user manually changes the
+        /// <see cref="t"/> parameter.
+        /// </summary>
+        /// <param name="info">Information about the clips to blend.</param>
+        /// <param name="t">Parameter that controls the blending, in range [0, 1]. t = 0 means left animation has full
+        ///                 influence, t = 1 means right animation has full influence.</param>
+        public void Blend1D(Blend1DInfo info, float t)
+        {
+            if (_native != null)
+                _native.Blend1D(info, t);
+        }
+
+        /// <summary>
+        /// Blend four animation clips between each other using bilinear interpolation. Unlike normal animations these
+        /// animations are not advanced with the progress of time, and is instead expected the user manually changes the
+        /// <see cref="t"/> parameter.
+        /// </summary>
+        /// <param name="info">Information about the clips to blend.</param>
+        /// <param name="t">Parameter that controls the blending, in range [(0, 0), (1, 1)]. t = (0, 0) means top left
+        ///                 animation has full influence, t = (0, 1) means top right animation has full influence, 
+        ///                 t = (1, 0) means bottom left animation has full influence, t = (1, 1) means bottom right
+        ///                 animation has full influence.
+        ///                 </param>
+        public void Blend2D(Blend2DInfo info, Vector2 t)
         {
         {
             if (_native != null)
             if (_native != null)
-                _native.Blend(clip, weight, fadeLength, layer);
+                _native.Blend2D(info, t);
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Fades the specified animation clip in, while fading other playing animations out, over the specified time 
-        /// period.
+        /// Fades the specified animation clip in, while fading other playing animation out, over the specified time period.
         /// </summary>
         /// </summary>
         /// <param name="clip">Clip to fade in.</param>
         /// <param name="clip">Clip to fade in.</param>
         /// <param name="fadeLength">Determines the time period over which the fade occurs. In seconds.</param>
         /// <param name="fadeLength">Determines the time period over which the fade occurs. In seconds.</param>
-        /// <param name="layer">Layer to play the clip in. Multiple clips can be playing at once in separate layers.</param>
-        /// <param name="playMode">Determines should all other animations be faded out, or just the ones on the same layer.
-        ///                        </param>
-        public void CrossFade(AnimationClip clip, float fadeLength = 0.0f, int layer = 0, 
-            AnimPlayMode playMode = AnimPlayMode.StopLayer)
+        public void CrossFade(AnimationClip clip, float fadeLength)
         {
         {
             if (_native != null)
             if (_native != null)
-                _native.CrossFade(clip, fadeLength, layer, playMode);
+                _native.CrossFade(clip, fadeLength);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -285,7 +346,7 @@ namespace BansheeEngine
             _native.Speed = serializableData.speed;
             _native.Speed = serializableData.speed;
 
 
             if (serializableData.defaultClip != null)
             if (serializableData.defaultClip != null)
-                _native.Play(serializableData.defaultClip, 0, AnimPlayMode.StopLayer);
+                _native.Play(serializableData.defaultClip);
 
 
             Renderable renderable = SceneObject.GetComponent<Renderable>();
             Renderable renderable = SceneObject.GetComponent<Renderable>();
             if (renderable == null)
             if (renderable == null)

+ 43 - 9
Source/MBansheeEngine/Animation/Interop/NativeAnimation.cs

@@ -23,31 +23,55 @@ namespace BansheeEngine
             set { Internal_SetSpeed(mCachedPtr, value); }
             set { Internal_SetSpeed(mCachedPtr, value); }
         }
         }
 
 
-        public void Play(AnimationClip clip, int layer, AnimPlayMode playMode)
+        public void Play(AnimationClip clip)
         {
         {
             IntPtr clipPtr = IntPtr.Zero;
             IntPtr clipPtr = IntPtr.Zero;
             if (clip != null)
             if (clip != null)
                 clipPtr = clip.GetCachedPtr();
                 clipPtr = clip.GetCachedPtr();
 
 
-            Internal_Play(mCachedPtr, clipPtr, layer, playMode);
+            Internal_Play(mCachedPtr, clipPtr);
         }
         }
 
 
-        public void Blend(AnimationClip clip, float weight, float fadeLength, int layer)
+        public void BlendAdditive(AnimationClip clip, float weight, float fadeLength, int layer)
         {
         {
             IntPtr clipPtr = IntPtr.Zero;
             IntPtr clipPtr = IntPtr.Zero;
             if (clip != null)
             if (clip != null)
                 clipPtr = clip.GetCachedPtr();
                 clipPtr = clip.GetCachedPtr();
 
 
-            Internal_Blend(mCachedPtr, clipPtr, weight, fadeLength, layer);
+            Internal_BlendAdditive(mCachedPtr, clipPtr, weight, fadeLength, layer);
         }
         }
 
 
-        public void CrossFade(AnimationClip clip, float fadeLength, int layer, AnimPlayMode playMode)
+        public void BlendSequential(BlendSequentialInfo info)
+        {
+            if (info == null)
+                return;
+
+            Internal_BlendSequential(mCachedPtr, info);
+        }
+
+        public void Blend1D(Blend1DInfo info, float t)
+        {
+            if (info == null)
+                return;
+
+            Internal_Blend1D(mCachedPtr, info, t);
+        }
+
+        public void Blend2D(Blend2DInfo info, Vector2 t)
+        {
+            if (info == null)
+                return;
+
+            Internal_Blend2D(mCachedPtr, info, ref t);
+        }
+
+        public void CrossFade(AnimationClip clip, float fadeLength)
         {
         {
             IntPtr clipPtr = IntPtr.Zero;
             IntPtr clipPtr = IntPtr.Zero;
             if (clip != null)
             if (clip != null)
                 clipPtr = clip.GetCachedPtr();
                 clipPtr = clip.GetCachedPtr();
 
 
-            Internal_CrossFade(mCachedPtr, clipPtr, fadeLength, layer, playMode);
+            Internal_CrossFade(mCachedPtr, clipPtr, fadeLength);
         }
         }
 
 
         public void Stop(int layer)
         public void Stop(int layer)
@@ -106,13 +130,23 @@ namespace BansheeEngine
         private static extern void Internal_SetSpeed(IntPtr thisPtr, float speed);
         private static extern void Internal_SetSpeed(IntPtr thisPtr, float speed);
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_Play(IntPtr thisPtr, IntPtr clipPtr, int layer, AnimPlayMode playMode);
+        private static extern void Internal_Play(IntPtr thisPtr, IntPtr clipPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_BlendAdditive(IntPtr thisPtr, IntPtr clipPtr, float weight, float fadeLength,
+            int layer);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_BlendSequential(IntPtr thisPtr, BlendSequentialInfo info);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Blend1D(IntPtr thisPtr, Blend1DInfo info, float t);
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_Blend(IntPtr thisPtr, IntPtr clipPtr, float weight, float fadeLength, int layer);
+        private static extern void Internal_Blend2D(IntPtr thisPtr, Blend2DInfo info, ref Vector2 t);
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_CrossFade(IntPtr thisPtr, IntPtr clipPtr, float fadeLength, int layer, AnimPlayMode playMode);
+        private static extern void Internal_CrossFade(IntPtr thisPtr, IntPtr clipPtr, float fadeLength);
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Stop(IntPtr thisPtr, int layer);
         private static extern void Internal_Stop(IntPtr thisPtr, int layer);

+ 85 - 3
Source/SBansheeEngine/Include/BsScriptAnimation.h

@@ -38,9 +38,12 @@ namespace BansheeEngine
 		static void internal_SetWrapMode(ScriptAnimation* thisPtr, AnimWrapMode wrapMode);
 		static void internal_SetWrapMode(ScriptAnimation* thisPtr, AnimWrapMode wrapMode);
 		static void internal_SetSpeed(ScriptAnimation* thisPtr, float speed);
 		static void internal_SetSpeed(ScriptAnimation* thisPtr, float speed);
 
 
-		static void internal_Play(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, UINT32 layer, AnimPlayMode playMode);
-		static void internal_Blend(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float weight, float fadeLength, UINT32 layer);
-		static void internal_CrossFade(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float fadeLength, UINT32 layer, AnimPlayMode playMode);
+		static void internal_Play(ScriptAnimation* thisPtr, ScriptAnimationClip* clip);
+		static void internal_BlendAdditive(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float weight, float fadeLength, UINT32 layer);
+		static void internal_BlendSequential(ScriptAnimation* thisPtr, MonoObject* info);
+		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_Stop(ScriptAnimation* thisPtr, UINT32 layer);
 		static void internal_Stop(ScriptAnimation* thisPtr, UINT32 layer);
 		static void internal_StopAll(ScriptAnimation* thisPtr);
 		static void internal_StopAll(ScriptAnimation* thisPtr);
@@ -50,5 +53,84 @@ namespace BansheeEngine
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 	};
 	};
 
 
+	/** Helper class for dealing with BlendSequentialInfo structure. */
+	class ScriptBlendSequentialInfo : public ScriptObject<ScriptBlendSequentialInfo>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "BlendSequentialInfo")
+
+		/** Converts managed split info to its native counterpart. */
+		static BlendSequentialInfo fromManaged(MonoObject* object);
+
+	private:
+		ScriptBlendSequentialInfo(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* clipsField;
+	};
+
+	/** Helper class for dealing with BlendSequentialClipInfo structure. */
+	class ScriptBlendSequentialClipInfo : public ScriptObject<ScriptBlendSequentialClipInfo>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "BlendSequentialClipInfo")
+
+		/** Converts managed split info to its native counterpart. */
+		static BlendSequentialClipInfo fromManaged(MonoObject* object);
+
+	private:
+		ScriptBlendSequentialClipInfo(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* clipField;
+		static MonoField* startTimeField;
+		static MonoField* endTimeField;
+		static MonoField* fadeTimeField;
+	};
+
+	/** Helper class for dealing with Blend1DInfo structure. */
+	class ScriptBlend1DInfo : public ScriptObject<ScriptBlend1DInfo>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "Blend1DInfo")
+
+		/** Converts managed split info to its native counterpart. */
+		static Blend1DInfo fromManaged(MonoObject* object);
+
+	private:
+		ScriptBlend1DInfo(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* leftClipField;
+		static MonoField* rightClipField;
+	};
+
+	/** Helper class for dealing with Blend2DInfo structure. */
+	class ScriptBlend2DInfo : public ScriptObject<ScriptBlend2DInfo>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "Blend2DInfo")
+
+		/** Converts managed split info to its native counterpart. */
+		static Blend2DInfo fromManaged(MonoObject* object);
+
+	private:
+		ScriptBlend2DInfo(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* topLeftClipField;
+		static MonoField* topRightClipField;
+		static MonoField* botLeftClipField;
+		static MonoField* botRightClipField;
+	};
+
 	/** @} */
 	/** @} */
 }
 }

+ 178 - 7
Source/SBansheeEngine/Source/BsScriptAnimation.cpp

@@ -28,7 +28,10 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetSpeed", &ScriptAnimation::internal_SetSpeed);
 		metaData.scriptClass->addInternalCall("Internal_SetSpeed", &ScriptAnimation::internal_SetSpeed);
 
 
 		metaData.scriptClass->addInternalCall("Internal_Play", &ScriptAnimation::internal_Play);
 		metaData.scriptClass->addInternalCall("Internal_Play", &ScriptAnimation::internal_Play);
-		metaData.scriptClass->addInternalCall("Internal_Blend", &ScriptAnimation::internal_Blend);
+		metaData.scriptClass->addInternalCall("Internal_BlendAdditive", &ScriptAnimation::internal_BlendAdditive);
+		metaData.scriptClass->addInternalCall("Internal_BlendSequential", &ScriptAnimation::internal_BlendSequential);
+		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_CrossFade", &ScriptAnimation::internal_CrossFade);
 
 
 		metaData.scriptClass->addInternalCall("Internal_Stop", &ScriptAnimation::internal_Stop);
 		metaData.scriptClass->addInternalCall("Internal_Stop", &ScriptAnimation::internal_Stop);
@@ -59,31 +62,50 @@ namespace BansheeEngine
 		thisPtr->getInternal()->setSpeed(speed);
 		thisPtr->getInternal()->setSpeed(speed);
 	}
 	}
 
 
-	void ScriptAnimation::internal_Play(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, UINT32 layer, AnimPlayMode playMode)
+	void ScriptAnimation::internal_Play(ScriptAnimation* thisPtr, ScriptAnimationClip* clip)
 	{
 	{
 		HAnimationClip nativeClip;
 		HAnimationClip nativeClip;
 		if (clip != nullptr)
 		if (clip != nullptr)
 			nativeClip = clip->getHandle();
 			nativeClip = clip->getHandle();
 
 
-		thisPtr->getInternal()->play(nativeClip, layer, playMode);
+		thisPtr->getInternal()->play(nativeClip);
 	}
 	}
 
 
-	void ScriptAnimation::internal_Blend(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float weight, float fadeLength, UINT32 layer)
+	void ScriptAnimation::internal_BlendAdditive(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float weight, 
+		float fadeLength, UINT32 layer)
 	{
 	{
 		HAnimationClip nativeClip;
 		HAnimationClip nativeClip;
 		if (clip != nullptr)
 		if (clip != nullptr)
 			nativeClip = clip->getHandle();
 			nativeClip = clip->getHandle();
 
 
-		thisPtr->getInternal()->blend(nativeClip, weight, fadeLength, layer);
+		thisPtr->getInternal()->blendAdditive(nativeClip, weight, fadeLength, layer);
 	}
 	}
 
 
-	void ScriptAnimation::internal_CrossFade(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float fadeLength, UINT32 layer, AnimPlayMode playMode)
+	void ScriptAnimation::internal_BlendSequential(ScriptAnimation* thisPtr, MonoObject* info)
+	{
+		BlendSequentialInfo nativeInfo = ScriptBlendSequentialInfo::fromManaged(info);
+		thisPtr->getInternal()->blendSequential(nativeInfo);
+	}
+
+	void ScriptAnimation::internal_Blend1D(ScriptAnimation* thisPtr, MonoObject* info, float t)
+	{
+		Blend1DInfo nativeInfo = ScriptBlend1DInfo::fromManaged(info);
+		thisPtr->getInternal()->blend1D(nativeInfo, t);
+	}
+
+	void ScriptAnimation::internal_Blend2D(ScriptAnimation* thisPtr, MonoObject* info, Vector2* t)
+	{
+		Blend2DInfo nativeInfo = ScriptBlend2DInfo::fromManaged(info);
+		thisPtr->getInternal()->blend2D(nativeInfo, *t);
+	}
+
+	void ScriptAnimation::internal_CrossFade(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, float fadeLength)
 	{
 	{
 		HAnimationClip nativeClip;
 		HAnimationClip nativeClip;
 		if (clip != nullptr)
 		if (clip != nullptr)
 			nativeClip = clip->getHandle();
 			nativeClip = clip->getHandle();
 
 
-		thisPtr->getInternal()->crossFade(nativeClip, fadeLength, layer, playMode);
+		thisPtr->getInternal()->crossFade(nativeClip, fadeLength);
 	}
 	}
 
 
 	void ScriptAnimation::internal_Stop(ScriptAnimation* thisPtr, UINT32 layer)
 	void ScriptAnimation::internal_Stop(ScriptAnimation* thisPtr, UINT32 layer)
@@ -118,4 +140,153 @@ namespace BansheeEngine
 
 
 		thisPtr->getInternal()->setState(nativeClip, *state);
 		thisPtr->getInternal()->setState(nativeClip, *state);
 	}
 	}
+
+	MonoField* ScriptBlendSequentialInfo::clipsField = nullptr;
+
+	ScriptBlendSequentialInfo::ScriptBlendSequentialInfo(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptBlendSequentialInfo::initRuntimeData()
+	{
+		clipsField = metaData.scriptClass->getField("clips");
+	}
+
+	BlendSequentialInfo ScriptBlendSequentialInfo::fromManaged(MonoObject* instance)
+	{
+		MonoArray* managedClipsArray = (MonoArray*)clipsField->getValueBoxed(instance);
+		if (managedClipsArray == nullptr)
+			return BlendSequentialInfo(0);
+
+		ScriptArray clipsArray(managedClipsArray);
+
+		UINT32 numClips = clipsArray.size();
+		BlendSequentialInfo output(numClips);
+		
+		for (UINT32 i = 0; i < numClips; i++)
+			output.clips[i] = ScriptBlendSequentialClipInfo::fromManaged(clipsArray.get<MonoObject*>(i));
+
+		return output;
+	}
+
+	MonoField* ScriptBlendSequentialClipInfo::clipField = nullptr;
+	MonoField* ScriptBlendSequentialClipInfo::startTimeField = nullptr;
+	MonoField* ScriptBlendSequentialClipInfo::endTimeField = nullptr;
+	MonoField* ScriptBlendSequentialClipInfo::fadeTimeField = nullptr;
+
+	ScriptBlendSequentialClipInfo::ScriptBlendSequentialClipInfo(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptBlendSequentialClipInfo::initRuntimeData()
+	{
+		clipField = metaData.scriptClass->getField("clip");
+		startTimeField = metaData.scriptClass->getField("startTime");
+		endTimeField = metaData.scriptClass->getField("endTime");
+		fadeTimeField = metaData.scriptClass->getField("fadeTime");
+	}
+
+	BlendSequentialClipInfo ScriptBlendSequentialClipInfo::fromManaged(MonoObject* instance)
+	{
+		BlendSequentialClipInfo output;
+
+		MonoObject* managedAnimClip = clipField->getValueBoxed(instance);
+		if(managedAnimClip)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedAnimClip);
+			output.clip = clip->getHandle();
+		}
+		
+		startTimeField->getValue(instance, &output.startTime);
+		endTimeField->getValue(instance, &output.endTime);
+		fadeTimeField->getValue(instance, &output.fadeTime);
+
+		return output;
+	}
+
+	MonoField* ScriptBlend1DInfo::leftClipField = nullptr;
+	MonoField* ScriptBlend1DInfo::rightClipField = nullptr;
+
+	ScriptBlend1DInfo::ScriptBlend1DInfo(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptBlend1DInfo::initRuntimeData()
+	{
+		leftClipField = metaData.scriptClass->getField("leftClip");
+		rightClipField = metaData.scriptClass->getField("rightClip");
+	}
+
+	Blend1DInfo ScriptBlend1DInfo::fromManaged(MonoObject* instance)
+	{
+		Blend1DInfo output;
+
+		MonoObject* managedLeftClip = leftClipField->getValueBoxed(instance);
+		if (managedLeftClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedLeftClip);
+			output.leftClip = clip->getHandle();
+		}
+
+		MonoObject* managedRightClip = rightClipField->getValueBoxed(instance);
+		if (managedRightClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedRightClip);
+			output.rightClip = clip->getHandle();
+		}
+
+		return output;
+	}
+
+	MonoField* ScriptBlend2DInfo::topLeftClipField = nullptr;
+	MonoField* ScriptBlend2DInfo::topRightClipField = nullptr;
+	MonoField* ScriptBlend2DInfo::botLeftClipField = nullptr;
+	MonoField* ScriptBlend2DInfo::botRightClipField = nullptr;
+
+	ScriptBlend2DInfo::ScriptBlend2DInfo(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptBlend2DInfo::initRuntimeData()
+	{
+		topLeftClipField = metaData.scriptClass->getField("topLeftClip");
+		topRightClipField = metaData.scriptClass->getField("topRightClip");
+		botLeftClipField = metaData.scriptClass->getField("botLeftClip");
+		botRightClipField = metaData.scriptClass->getField("botRightClip");
+	}
+
+	Blend2DInfo ScriptBlend2DInfo::fromManaged(MonoObject* instance)
+	{
+		Blend2DInfo output;
+
+		MonoObject* managedTopLeftClip = topLeftClipField->getValueBoxed(instance);
+		if (managedTopLeftClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedTopLeftClip);
+			output.topLeftClip = clip->getHandle();
+		}
+
+		MonoObject* managedTopRightClip = topRightClipField->getValueBoxed(instance);
+		if (managedTopRightClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedTopRightClip);
+			output.topRightClip = clip->getHandle();
+		}
+
+		MonoObject* managedBotLeftClip = botLeftClipField->getValueBoxed(instance);
+		if (managedBotLeftClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedBotLeftClip);
+			output.botLeftClip = clip->getHandle();
+		}
+
+		MonoObject* managedBotRightClip = botRightClipField->getValueBoxed(instance);
+		if (managedBotRightClip != nullptr)
+		{
+			ScriptAnimationClip* clip = ScriptAnimationClip::toNative(managedBotRightClip);
+			output.botRightClip = clip->getHandle();
+		}
+
+		return output;
+	}
 }
 }