Przeglądaj źródła

Additive animations

BearishSun 9 lat temu
rodzic
commit
6fd40952e9

+ 15 - 5
Source/BansheeCore/Include/BsAnimationClip.h

@@ -140,6 +140,12 @@ namespace BansheeEngine
 		 */
 		 */
 		void getBoneMapping(const Skeleton& skeleton, AnimationCurveMapping* mapping) const;
 		void getBoneMapping(const Skeleton& skeleton, AnimationCurveMapping* mapping) const;
 
 
+		/** 
+		 * Checks are the curves contained within the clip additive. Additive clips are intended to be added on top of
+		 * other clips.
+		 */
+		bool isAdditive() const { return mIsAdditive; }
+
 		/** 
 		/** 
 		 * Returns a version that can be used for detecting modifications on the clip by external systems. Whenever the clip
 		 * Returns a version that can be used for detecting modifications on the clip by external systems. Whenever the clip
 		 * is modified the version is increased by one.
 		 * is modified the version is increased by one.
@@ -150,14 +156,16 @@ namespace BansheeEngine
 		 * Creates an animation clip with no curves. After creation make sure to register some animation curves before
 		 * Creates an animation clip with no curves. After creation make sure to register some animation curves before
 		 * using it. 
 		 * using it. 
 		 */
 		 */
-		static HAnimationClip create();
+		static HAnimationClip create(bool isAdditive = false);
 
 
 		/** 
 		/** 
 		 * Creates an animation clip with specified curves.
 		 * Creates an animation clip with specified curves.
 		 *
 		 *
-		 * @param[in]	curves	Curves to initialize the animation with.
+		 * @param[in]	curves		Curves to initialize the animation with.
+		 * @param[in]	isAdditive	Determines does the clip contain additive curve data. This will change the behaviour
+		 *							how is the clip blended with other animations.
 		 */
 		 */
-		static HAnimationClip create(const SPtr<AnimationCurves>& curves);
+		static HAnimationClip create(const SPtr<AnimationCurves>& curves, bool isAdditive = false);
 
 
 	public: // ***** INTERNAL ******
 	public: // ***** INTERNAL ******
 		/** @name Internal
 		/** @name Internal
@@ -165,13 +173,13 @@ namespace BansheeEngine
 		 */
 		 */
 
 
 		/** Creates a new AnimationClip without initializing it. Use create() for normal use. */
 		/** Creates a new AnimationClip without initializing it. Use create() for normal use. */
-		static SPtr<AnimationClip> _createPtr(const SPtr<AnimationCurves>& curves);
+		static SPtr<AnimationClip> _createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive = false);
 
 
 		/** @} */
 		/** @} */
 
 
 	protected:
 	protected:
 		AnimationClip();
 		AnimationClip();
-		AnimationClip(const SPtr<AnimationCurves>& curves);
+		AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive);
 
 
 		/** @copydoc Resource::initialize() */
 		/** @copydoc Resource::initialize() */
 		void initialize() override;
 		void initialize() override;
@@ -193,6 +201,8 @@ namespace BansheeEngine
 		 */
 		 */
 		UnorderedMap<String, UINT32[4]> mNameMapping;
 		UnorderedMap<String, UINT32[4]> mNameMapping;
 
 
+		bool mIsAdditive;
+
 		/************************************************************************/
 		/************************************************************************/
 		/* 								SERIALIZATION                      		*/
 		/* 								SERIALIZATION                      		*/
 		/************************************************************************/
 		/************************************************************************/

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

@@ -22,6 +22,7 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_PLAIN_NAMED(rotationCurves, mCurves->rotation, 1)
 			BS_RTTI_MEMBER_PLAIN_NAMED(rotationCurves, mCurves->rotation, 1)
 			BS_RTTI_MEMBER_PLAIN_NAMED(scaleCurves, mCurves->scale, 2)
 			BS_RTTI_MEMBER_PLAIN_NAMED(scaleCurves, mCurves->scale, 2)
 			BS_RTTI_MEMBER_PLAIN_NAMED(genericCurves, mCurves->generic, 3)
 			BS_RTTI_MEMBER_PLAIN_NAMED(genericCurves, mCurves->generic, 3)
+			BS_RTTI_MEMBER_PLAIN(mIsAdditive, 4)
 		BS_END_RTTI_MEMBERS
 		BS_END_RTTI_MEMBERS
 	public:
 	public:
 		AnimationClipRTTI()
 		AnimationClipRTTI()

+ 4 - 3
Source/BansheeCore/Include/BsSkeleton.h

@@ -60,10 +60,11 @@ namespace BansheeEngine
 		UINT8 index; /**< Unique index of the animation layer. */
 		UINT8 index; /**< Unique index of the animation layer. */
 
 
 		/**
 		/**
-		 * 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.
+		 * If true animations from this layer will be added on top of other layers using the per-state weights. If false
+		 * the weights will be normalized, animations will be blended with each other according to the normalized weights
+		 * and then added on top of other layers.
 		 */
 		 */
-		bool normalizeWeights;
+		bool additive;
 	};
 	};
 
 
 	/** 
 	/** 

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

@@ -76,7 +76,7 @@ namespace BansheeEngine
 					AnimationStateLayer& newLayer = tempLayers.back();
 					AnimationStateLayer& newLayer = tempLayers.back();
 
 
 					newLayer.index = clipInfo.state.layer;
 					newLayer.index = clipInfo.state.layer;
-					newLayer.normalizeWeights = false; // TODO - This needs to be deduced from PlayingClipInfo
+					newLayer.additive = !clipInfo.clip->isAdditive();
 				}
 				}
 			}
 			}
 
 
@@ -252,6 +252,7 @@ namespace BansheeEngine
 
 
 		mDirty |= AnimDirtyStateFlag::Value;
 		mDirty |= AnimDirtyStateFlag::Value;
 	}
 	}
+
 	void Animation::setSpeed(float speed)
 	void Animation::setSpeed(float speed)
 	{
 	{
 		mDefaultSpeed = speed;
 		mDefaultSpeed = speed;

+ 9 - 12
Source/BansheeCore/Source/BsAnimationClip.cpp

@@ -8,28 +8,25 @@
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	AnimationClip::AnimationClip()
 	AnimationClip::AnimationClip()
-		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>())
+		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>()), mIsAdditive(false)
 	{
 	{
 
 
 	}
 	}
 
 
-	AnimationClip::AnimationClip(const SPtr<AnimationCurves>& curves)
-		: Resource(false), mVersion(0), mCurves(curves)
+	AnimationClip::AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive)
+		: Resource(false), mVersion(0), mCurves(curves), mIsAdditive(isAdditive)
 	{
 	{
 
 
 	}
 	}
 
 
-	HAnimationClip AnimationClip::create()
+	HAnimationClip AnimationClip::create(bool isAdditive)
 	{
 	{
-		SPtr<AnimationClip> newClip = createEmpty();
-		newClip->initialize();
-
-		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(newClip));
+		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(_createPtr(nullptr, isAdditive)));
 	}
 	}
 
 
-	HAnimationClip AnimationClip::create(const SPtr<AnimationCurves>& curves)
+	HAnimationClip AnimationClip::create(const SPtr<AnimationCurves>& curves, bool isAdditive)
 	{
 	{
-		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(_createPtr(curves)));
+		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(_createPtr(curves, isAdditive)));
 	}
 	}
 
 
 	SPtr<AnimationClip> AnimationClip::createEmpty()
 	SPtr<AnimationClip> AnimationClip::createEmpty()
@@ -42,9 +39,9 @@ namespace BansheeEngine
 		return newClip;
 		return newClip;
 	}
 	}
 
 
-	SPtr<AnimationClip> AnimationClip::_createPtr(const SPtr<AnimationCurves>& curves)
+	SPtr<AnimationClip> AnimationClip::_createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive)
 	{
 	{
-		AnimationClip* rawPtr = new (bs_alloc<AnimationClip>()) AnimationClip(curves);
+		AnimationClip* rawPtr = new (bs_alloc<AnimationClip>()) AnimationClip(curves, isAdditive);
 
 
 		SPtr<AnimationClip> newClip = bs_core_ptr<AnimationClip>(rawPtr);
 		SPtr<AnimationClip> newClip = bs_core_ptr<AnimationClip>(rawPtr);
 		newClip->_setThisPtr(newClip);
 		newClip->_setThisPtr(newClip);

+ 24 - 1
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -122,6 +122,22 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
+	/** Calculates the difference between two values. */
+	float getDiff(float lhs, float rhs)
+	{
+		return lhs - rhs;
+	}
+
+	Vector3 getDiff(const Vector3& lhs, const Vector3& rhs)
+	{
+		return lhs - rhs;
+	}
+
+	Quaternion getDiff(const Quaternion& lhs, const Quaternion& rhs)
+	{
+		return rhs.inverse() * lhs;
+	}
+
 	template <class T>
 	template <class T>
 	const UINT32 TAnimationCurve<T>::CACHE_LOOKAHEAD = 3;
 	const UINT32 TAnimationCurve<T>::CACHE_LOOKAHEAD = 3;
 
 
@@ -479,7 +495,14 @@ namespace BansheeEngine
 	template <class T>
 	template <class T>
 	void TAnimationCurve<T>::makeAdditive()
 	void TAnimationCurve<T>::makeAdditive()
 	{
 	{
-		
+		if (mKeyframes.size() < 2)
+			return;
+
+		const KeyFrame& refKey = mKeyframes[0];
+		UINT32 numKeys = (UINT32)mKeyframes.size();
+
+		for(UINT32 i = 1; i < numKeys; i++)
+			mKeyframes[i].value = getDiff(mKeyframes[i].value, refKey.value);
 	}
 	}
 
 
 	template class TAnimationCurve<Vector3>;
 	template class TAnimationCurve<Vector3>;

+ 39 - 9
Source/BansheeCore/Source/BsSkeleton.cpp

@@ -102,7 +102,7 @@ namespace BansheeEngine
 
 
 			AnimationStateLayer layer;
 			AnimationStateLayer layer;
 			layer.index = 0;
 			layer.index = 0;
-			layer.normalizeWeights = false;
+			layer.additive = false;
 			layer.states = &state;
 			layer.states = &state;
 			layer.numStates = 1;
 			layer.numStates = 1;
 
 
@@ -132,11 +132,11 @@ namespace BansheeEngine
 			const AnimationStateLayer& layer = layers[i];
 			const AnimationStateLayer& layer = layers[i];
 
 
 			float invLayerWeight;
 			float invLayerWeight;
-			if (layer.normalizeWeights)
+			if (layer.additive)
 			{
 			{
 				float weightSum = 0.0f;
 				float weightSum = 0.0f;
 				for (UINT32 j = 0; j < layer.numStates; j++)
 				for (UINT32 j = 0; j < layer.numStates; j++)
-					weightSum += layer.normalizeWeights;
+					weightSum += layer.states[j].weight;
 
 
 				invLayerWeight = 1.0f / weightSum;
 				invLayerWeight = 1.0f / weightSum;
 			}
 			}
@@ -145,7 +145,7 @@ namespace BansheeEngine
 
 
 			for (UINT32 j = 0; j < layer.numStates; j++)
 			for (UINT32 j = 0; j < layer.numStates; j++)
 			{
 			{
-				const AnimationState& state = layer.states[i];
+				const AnimationState& state = layer.states[j];
 
 
 				float normWeight = state.weight * invLayerWeight;
 				float normWeight = state.weight * invLayerWeight;
 				for (UINT32 k = 0; k < mNumBones; k++)
 				for (UINT32 k = 0; k < mNumBones; k++)
@@ -157,12 +157,11 @@ namespace BansheeEngine
 						const TAnimationCurve<Vector3>& curve = state.curves->position[mapping.position].curve;
 						const TAnimationCurve<Vector3>& curve = state.curves->position[mapping.position].curve;
 						localPose.positions[k] += curve.evaluate(state.time, state.positionCaches[k], state.loop) * normWeight;
 						localPose.positions[k] += curve.evaluate(state.time, state.positionCaches[k], state.loop) * normWeight;
 					}
 					}
+				}
 
 
-					if (mapping.rotation != (UINT32)-1)
-					{
-						const TAnimationCurve<Quaternion>& curve = state.curves->rotation[mapping.rotation].curve;
-						localPose.rotations[k] += curve.evaluate(state.time, state.rotationCaches[k], state.loop) * normWeight;
-					}
+				for (UINT32 k = 0; k < mNumBones; k++)
+				{
+					const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];
 
 
 					if (mapping.scale != (UINT32)-1)
 					if (mapping.scale != (UINT32)-1)
 					{
 					{
@@ -170,6 +169,37 @@ namespace BansheeEngine
 						localPose.scales[k] += curve.evaluate(state.time, state.scaleCaches[k], state.loop) * normWeight;
 						localPose.scales[k] += curve.evaluate(state.time, state.scaleCaches[k], state.loop) * normWeight;
 					}
 					}
 				}
 				}
+
+				if(layer.additive)
+				{
+					for (UINT32 k = 0; k < mNumBones; k++)
+					{
+						const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];
+
+						if (mapping.rotation != (UINT32)-1)
+						{
+							const TAnimationCurve<Quaternion>& curve = state.curves->rotation[mapping.rotation].curve;
+
+							Quaternion value = curve.evaluate(state.time, state.rotationCaches[k], state.loop);
+							value = Quaternion::lerp(normWeight, Quaternion::IDENTITY, value);
+
+							localPose.rotations[k] *= value;
+						}
+					}
+				}
+				else
+				{
+					for (UINT32 k = 0; k < mNumBones; k++)
+					{
+						const AnimationCurveMapping& mapping = state.boneToCurveMapping[k];
+
+						if (mapping.rotation != (UINT32)-1)
+						{
+							const TAnimationCurve<Quaternion>& curve = state.curves->rotation[mapping.rotation].curve;
+							localPose.rotations[k] += curve.evaluate(state.time, state.rotationCaches[k], state.loop) * normWeight;
+						}
+					}
+				}
 			}
 			}
 		}
 		}
 
 

+ 12 - 0
Source/BansheeFBXImporter/Include/BsFBXImportData.h

@@ -130,6 +130,18 @@ namespace BansheeEngine
 		Vector<FBXBlendShapeAnimation> blendShapeAnimations;
 		Vector<FBXBlendShapeAnimation> blendShapeAnimations;
 	};
 	};
 
 
+	/** All information required for creating an animation clip. */
+	struct FBXAnimationClipData
+	{
+		FBXAnimationClipData(const String& name, bool isAdditive, const SPtr<AnimationCurves>& curves)
+			:name(name), isAdditive(isAdditive), curves(curves)
+		{ }
+
+		String name;
+		bool isAdditive;
+		SPtr<AnimationCurves> curves;
+	};
+
 	/**	Imported mesh data. */
 	/**	Imported mesh data. */
 	struct FBXImportMesh
 	struct FBXImportMesh
 	{
 	{

+ 2 - 2
Source/BansheeFBXImporter/Include/BsFBXImporter.h

@@ -52,7 +52,7 @@ namespace BansheeEngine
 		 * Reads the FBX file and outputs mesh data from the read file. Sub-mesh information will be output in @p subMeshes.
 		 * Reads the FBX file and outputs mesh data from the read file. Sub-mesh information will be output in @p subMeshes.
 		 */
 		 */
 		SPtr<RendererMeshData> importMeshData(const Path& filePath, SPtr<const ImportOptions> importOptions, 
 		SPtr<RendererMeshData> importMeshData(const Path& filePath, SPtr<const ImportOptions> importOptions, 
-			Vector<SubMesh>& subMeshes, UnorderedMap<String, SPtr<AnimationCurves>>& animationClips, SPtr<Skeleton>& skeleton);
+			Vector<SubMesh>& subMeshes, Vector<FBXAnimationClipData>& animationClips, SPtr<Skeleton>& skeleton);
 
 
 		/**
 		/**
 		 * Loads the data from the file at the provided path into the provided FBX scene. Returns false if the file
 		 * Loads the data from the file at the provided path into the provided FBX scene. Returns false if the file
@@ -102,7 +102,7 @@ namespace BansheeEngine
 
 
 		/** Converts FBX animation clips into engine-ready animation curve format. */
 		/** Converts FBX animation clips into engine-ready animation curve format. */
 		void convertAnimations(const Vector<FBXAnimationClip>& clips, const Vector<AnimationSplitInfo>& splits, 
 		void convertAnimations(const Vector<FBXAnimationClip>& clips, const Vector<AnimationSplitInfo>& splits, 
-			UnorderedMap<String, SPtr<AnimationCurves>>& output);
+			Vector<FBXAnimationClipData>& output);
 
 
 		/**	Converts a set of curves containing rotation in euler angles into a set of curves using	quaternion rotation. */
 		/**	Converts a set of curves containing rotation in euler angles into a set of curves using	quaternion rotation. */
 		void eulerToQuaternionCurves(FBXAnimationCurve(&eulerCurves)[3], FBXAnimationCurve(&quatCurves)[4]);
 		void eulerToQuaternionCurves(FBXAnimationCurve(&eulerCurves)[3], FBXAnimationCurve(&quatCurves)[4]);

+ 14 - 10
Source/BansheeFBXImporter/Source/BsFBXImporter.cpp

@@ -116,7 +116,7 @@ namespace BansheeEngine
 	SPtr<Resource> FBXImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions)
 	SPtr<Resource> FBXImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions)
 	{
 	{
 		Vector<SubMesh> subMeshes;
 		Vector<SubMesh> subMeshes;
-		UnorderedMap<String, SPtr<AnimationCurves>> dummy;
+		Vector<FBXAnimationClipData> dummy;
 		SPtr<Skeleton> skeleton;
 		SPtr<Skeleton> skeleton;
 		SPtr<RendererMeshData> rendererMeshData = importMeshData(filePath, importOptions, subMeshes, dummy, skeleton);
 		SPtr<RendererMeshData> rendererMeshData = importMeshData(filePath, importOptions, subMeshes, dummy, skeleton);
 
 
@@ -137,7 +137,7 @@ namespace BansheeEngine
 	Vector<SubResourceRaw> FBXImporter::importAll(const Path& filePath, SPtr<const ImportOptions> importOptions)
 	Vector<SubResourceRaw> FBXImporter::importAll(const Path& filePath, SPtr<const ImportOptions> importOptions)
 	{
 	{
 		Vector<SubMesh> subMeshes;
 		Vector<SubMesh> subMeshes;
-		UnorderedMap<String, SPtr<AnimationCurves>> animationClips;
+		Vector<FBXAnimationClipData> animationClips;
 		SPtr<Skeleton> skeleton;
 		SPtr<Skeleton> skeleton;
 		SPtr<RendererMeshData> rendererMeshData = importMeshData(filePath, importOptions, subMeshes, animationClips, skeleton);
 		SPtr<RendererMeshData> rendererMeshData = importMeshData(filePath, importOptions, subMeshes, animationClips, skeleton);
 
 
@@ -177,9 +177,9 @@ namespace BansheeEngine
 
 
 			for(auto& entry : animationClips)
 			for(auto& entry : animationClips)
 			{
 			{
-				SPtr<AnimationClip> clip = AnimationClip::_createPtr(entry.second);
+				SPtr<AnimationClip> clip = AnimationClip::_createPtr(entry.curves, entry.isAdditive);
 
 
-				output.push_back({ toWString(entry.first), clip });
+				output.push_back({ toWString(entry.name), clip });
 			}
 			}
 		}
 		}
 
 
@@ -187,7 +187,7 @@ namespace BansheeEngine
 	}
 	}
 
 
 	SPtr<RendererMeshData> FBXImporter::importMeshData(const Path& filePath, SPtr<const ImportOptions> importOptions, 
 	SPtr<RendererMeshData> FBXImporter::importMeshData(const Path& filePath, SPtr<const ImportOptions> importOptions, 
-		Vector<SubMesh>& subMeshes, UnorderedMap<String, SPtr<AnimationCurves>>& animation, SPtr<Skeleton>& skeleton)
+		Vector<SubMesh>& subMeshes, Vector<FBXAnimationClipData>& animation, SPtr<Skeleton>& skeleton)
 	{
 	{
 		FbxScene* fbxScene = nullptr;
 		FbxScene* fbxScene = nullptr;
 
 
@@ -423,8 +423,10 @@ namespace BansheeEngine
 	}
 	}
 
 
 	void FBXImporter::convertAnimations(const Vector<FBXAnimationClip>& clips, const Vector<AnimationSplitInfo>& splits,
 	void FBXImporter::convertAnimations(const Vector<FBXAnimationClip>& clips, const Vector<AnimationSplitInfo>& splits,
-		UnorderedMap<String, SPtr<AnimationCurves>>& output)
+		Vector<FBXAnimationClipData>& output)
 	{
 	{
+		UnorderedSet<String> names;
+
 		bool isFirstClip = true;
 		bool isFirstClip = true;
 		for (auto& clip : clips)
 		for (auto& clip : clips)
 		{
 		{
@@ -548,13 +550,14 @@ namespace BansheeEngine
 					// Search for a unique name
 					// Search for a unique name
 					String name = split.name;
 					String name = split.name;
 					UINT32 attemptIdx = 0;
 					UINT32 attemptIdx = 0;
-					while (output.find(name) != output.end())
+					while (names.find(name) != names.end())
 					{
 					{
 						name = clip.name + "_" + toString(attemptIdx);
 						name = clip.name + "_" + toString(attemptIdx);
 						attemptIdx++;
 						attemptIdx++;
 					}
 					}
 
 
-					output.insert(std::make_pair(name, splitClipCurve));
+					names.insert(name);
+					output.push_back(FBXAnimationClipData(name, split.isAdditive, splitClipCurve));
 				}
 				}
 			}
 			}
 			else
 			else
@@ -562,13 +565,14 @@ namespace BansheeEngine
 				// Search for a unique name
 				// Search for a unique name
 				String name = clip.name;
 				String name = clip.name;
 				UINT32 attemptIdx = 0;
 				UINT32 attemptIdx = 0;
-				while(output.find(name) != output.end())
+				while(names.find(name) != names.end())
 				{
 				{
 					name = clip.name + "_" + toString(attemptIdx);
 					name = clip.name + "_" + toString(attemptIdx);
 					attemptIdx++;
 					attemptIdx++;
 				}
 				}
 
 
-				output.insert(std::make_pair(name, curves));
+				names.insert(name);
+				output.push_back(FBXAnimationClipData(name, false, curves));
 			}
 			}
 
 
 			isFirstClip = false;
 			isFirstClip = false;

+ 44 - 2
Source/BansheeUtility/Include/BsQuaternion.h

@@ -245,6 +245,21 @@ namespace BansheeEngine
 			return *this;
 			return *this;
 		}
 		}
 
 
+		Quaternion& operator*= (const Quaternion& rhs)
+		{
+			float newW = w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z;
+			float newX = w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y;
+			float newY = w * rhs.y + y * rhs.w + z * rhs.x - x * rhs.z;
+			float newZ = w * rhs.z + z * rhs.w + x * rhs.y - y * rhs.x;
+
+			w = newW;
+			x = newX;
+			y = newY;
+			z = newZ;
+
+			return *this;
+		}
+
 		friend Quaternion operator* (float lhs, const Quaternion& rhs)
 		friend Quaternion operator* (float lhs, const Quaternion& rhs)
 		{
 		{
 			return Quaternion(lhs * rhs.w, lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
 			return Quaternion(lhs * rhs.w, lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
@@ -297,12 +312,39 @@ namespace BansheeEngine
 			return Math::isNaN(x) || Math::isNaN(y) || Math::isNaN(z) || Math::isNaN(w);
 			return Math::isNaN(x) || Math::isNaN(y) || Math::isNaN(z) || Math::isNaN(w);
 		}
 		}
 
 
+		/** Calculates the dot product between two quaternions. */
+		static float dot(const Quaternion& lhs, const Quaternion& rhs)
+		{
+			return lhs.w * rhs.w + lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
+		}
+
+		/** Normalizes the provided quaternion. */
+		static Quaternion normalize(const Quaternion& q)
+		{
+			float len = dot(q, q);
+			float factor = 1.0f / Math::sqrt(len);
+
+			return q * factor;
+		}
+
         /**
         /**
          * Performs spherical interpolation between two quaternions. Spherical interpolation neatly interpolates between 
          * Performs spherical interpolation between two quaternions. Spherical interpolation neatly interpolates between 
 		 * two rotations without modifying the size of the vector it is applied to (unlike linear interpolation).
 		 * two rotations without modifying the size of the vector it is applied to (unlike linear interpolation).
          */
          */
-        static Quaternion slerp(float t, const Quaternion& p,
-            const Quaternion& q, bool shortestPath = true);
+        static Quaternion slerp(float t, const Quaternion& p, const Quaternion& q, bool shortestPath = true);
+
+		/** 
+		 * Linearly interpolates between the two quaternions using @p t. t should be in [0, 1] range, where t = 0 
+		 * corresponds to the left vector, while t = 1 corresponds to the right vector.
+		 */
+		static Quaternion lerp(float t, const Quaternion& a, const Quaternion& b)
+		{
+			float d = dot(a, b);
+			float flip = d >= 0.0f ? 1.0f : -1.0f;
+			
+			Quaternion output = flip * (1.0f - t) * a + t * b;
+			return normalize(output);
+		}
 
 
         /** Gets the shortest arc quaternion to rotate this vector to the destination vector. */
         /** Gets the shortest arc quaternion to rotate this vector to the destination vector. */
         static Quaternion getRotationFromTo(const Vector3& from, const Vector3& dest, const Vector3& fallbackAxis = Vector3::ZERO);
         static Quaternion getRotationFromTo(const Vector3& from, const Vector3& dest, const Vector3& fallbackAxis = Vector3::ZERO);

+ 9 - 0
Source/BansheeUtility/Include/BsVector3.h

@@ -389,6 +389,15 @@ namespace BansheeEngine
 				a.x * b.y - a.y * b.x);
 				a.x * b.y - a.y * b.x);
         }
         }
 
 
+		/** 
+		 * Linearly interpolates between the two vectors using @p t. t should be in [0, 1] range, where t = 0 corresponds
+		 * to the left vector, while t = 1 corresponds to the right vector. 
+		 */
+		static Vector3 lerp(float t, const Vector3& a, const Vector3& b)
+		{
+			return (1.0f - t) * a + t * b;
+		}
+
 		/** Checks are any of the vector components not a number. */
 		/** Checks are any of the vector components not a number. */
 		inline bool isNaN() const;
 		inline bool isNaN() const;