Ver código fonte

Added root motion property to animation clip and mesh import options

BearishSun 9 anos atrás
pai
commit
ab1fe95dc9

+ 34 - 3
Source/BansheeCore/Include/BsAnimationClip.h

@@ -73,6 +73,18 @@ namespace BansheeEngine
 		Vector<TNamedAnimationCurve<float>> generic;
 	};
 
+	/** Contains a set of animation curves used for moving and rotating the root bone. */
+	struct RootMotion
+	{
+		RootMotion() { }
+		RootMotion(const TAnimationCurve<Vector3>& position, const TAnimationCurve<Quaternion>& rotation)
+			:position(position), rotation(rotation)
+		{ }
+
+		TAnimationCurve<Vector3> position;
+		TAnimationCurve<Quaternion> rotation;
+	};
+
 	/** Event that is triggered when animation reaches a certain point. */
 	struct AnimationEvent
 	{
@@ -124,6 +136,13 @@ namespace BansheeEngine
 		/** Sets events that will be triggered as the animation is playing. */
 		void setEvents(const Vector<AnimationEvent>& events) { mEvents = events; }
 
+		/** 
+		 * Returns a set of curves containing motion of the root bone. This allows the user to evaluate the root bone
+		 * animation curves manually, instead of through the normal animation process. This property is only available
+		 * if animation clip was imported with root motion import enabled. 
+		 */
+		SPtr<RootMotion> getRootMotion() const { return mRootMotion; }
+
 		/**
 		 * Maps skeleton bone names to animation curve names, and returns a set of indices that can be easily used for
 		 * locating an animation curve based on the bone index.
@@ -200,8 +219,11 @@ namespace BansheeEngine
 		 *							how is the clip blended with other animations.
 		 * @param[in]	sampleRate	If animation uses evenly spaced keyframes, number of samples per second. Not relevant
 		 *							if keyframes are unevenly spaced.
+		 * @param[in]	rootMotion	Optional set of curves that can be used for animating the root bone. Not used by the
+		 *							animation system directly but is instead provided to the user for manual evaluation.
 		 */
-		static HAnimationClip create(const SPtr<AnimationCurves>& curves, bool isAdditive = false, UINT32 sampleRate = 1);
+		static HAnimationClip create(const SPtr<AnimationCurves>& curves, bool isAdditive = false, UINT32 sampleRate = 1, 
+			const SPtr<RootMotion>& rootMotion = nullptr);
 
 	public: // ***** INTERNAL ******
 		/** @name Internal
@@ -209,13 +231,15 @@ namespace BansheeEngine
 		 */
 
 		/** Creates a new AnimationClip without initializing it. Use create() for normal use. */
-		static SPtr<AnimationClip> _createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive = false, UINT32 sampleRate = 1);
+		static SPtr<AnimationClip> _createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive = false, 
+			UINT32 sampleRate = 1, const SPtr<RootMotion>& rootMotion = nullptr);
 
 		/** @} */
 
 	protected:
 		AnimationClip();
-		AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate);
+		AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate, 
+			const SPtr<RootMotion>& rootMotion);
 
 		/** @copydoc Resource::initialize() */
 		void initialize() override;
@@ -235,6 +259,13 @@ namespace BansheeEngine
 		 */
 		SPtr<AnimationCurves> mCurves;
 
+		/**
+		 * A set of curves containing motion of the root bone. If this is non-empty it should be true that mCurves does not
+		 * contain animation curves for the root bone. Root motion will not be evaluated through normal animation process
+		 * but is instead provided for the user for manual evaluation. 
+		 */
+		SPtr<RootMotion> mRootMotion;
+
 		/** 
 		 * Contains a map from curve name to curve index. Indices are stored as specified in CurveType enum. 
 		 */

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

@@ -75,6 +75,8 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_PLAIN(mLength, 5)
 			BS_RTTI_MEMBER_PLAIN(mEvents, 6)
 			BS_RTTI_MEMBER_PLAIN(mSampleRate, 7)
+			BS_RTTI_MEMBER_PLAIN_NAMED(rootMotionPos, mRootMotion->position, 8)
+			BS_RTTI_MEMBER_PLAIN_NAMED(rootMotionRot, mRootMotion->rotation, 9)
 		BS_END_RTTI_MEMBERS
 	public:
 		AnimationClipRTTI()

+ 17 - 1
Source/BansheeCore/Include/BsMeshImportOptions.h

@@ -135,7 +135,7 @@ namespace BansheeEngine
 		Vector<ImportedAnimationEvents> getAnimationEvents() const { return mAnimationEvents; }
 
 		/**	
-		 * Enables or disabled keyframe reduction. Keyframe reduction will reduce the number of key-frames in an animation
+		 * Enables or disables keyframe reduction. Keyframe reduction will reduce the number of key-frames in an animation
 		 * clip by removing identical keyframes, and therefore reducing the size of the clip.
 		 */
 		void setKeyFrameReduction(bool enabled) { mReduceKeyFrames = enabled; }
@@ -147,6 +147,21 @@ namespace BansheeEngine
 		 */
 		bool getKeyFrameReduction() const { return mReduceKeyFrames; }
 
+		/**	
+		 * Enables or disables import of root motion curves. When enabled, any animation curves in imported animations 
+		 * affecting the root bone will be available through a set of separate curves in AnimationClip, and they won't be
+		 * evaluated through normal animation process. Instead it is expected that the user evaluates the curves manually
+		 * and applies them as required.
+		 */
+		void setImportRootMotion(bool enabled) { mImportRootMotion = enabled; }
+
+		/**	
+		 * Checks is root motion import enabled.
+		 *
+		 * @see	setImportRootMotion
+		 */
+		bool getImportRootMotion() const { return mImportRootMotion; }
+
 	private:
 		bool mCPUReadable;
 		bool mImportNormals;
@@ -155,6 +170,7 @@ namespace BansheeEngine
 		bool mImportSkin;
 		bool mImportAnimation;
 		bool mReduceKeyFrames;
+		bool mImportRootMotion;
 		float mImportScale;
 		CollisionMeshType mCollisionMeshType;
 		Vector<AnimationSplitInfo> mAnimationSplits;

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

@@ -29,6 +29,7 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_REFL_ARRAY(mAnimationSplits, 8)
 			BS_RTTI_MEMBER_PLAIN(mReduceKeyFrames, 9)
 			BS_RTTI_MEMBER_REFL_ARRAY(mAnimationEvents, 10)
+			BS_RTTI_MEMBER_PLAIN(mImportRootMotion, 11)
 		BS_END_RTTI_MEMBERS
 	public:
 		MeshImportOptionsRTTI()

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

@@ -81,27 +81,38 @@ namespace BansheeEngine
 	}
 
 	AnimationClip::AnimationClip()
-		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>()), mIsAdditive(false), mLength(0.0f)
-		, mSampleRate(1)
+		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>())
+		, mRootMotion(bs_shared_ptr_new<RootMotion>()), mIsAdditive(false), mLength(0.0f), mSampleRate(1)
 	{
 
 	}
 
-	AnimationClip::AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate)
-		: Resource(false), mVersion(0), mCurves(curves), mIsAdditive(isAdditive), mLength(0.0f), mSampleRate(sampleRate)
+	AnimationClip::AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate, 
+		const SPtr<RootMotion>& rootMotion)
+		: Resource(false), mVersion(0), mCurves(curves), mRootMotion(rootMotion), mIsAdditive(isAdditive), mLength(0.0f)
+		, mSampleRate(sampleRate)
 	{
+		if (mCurves == nullptr)
+			mCurves = bs_shared_ptr_new<AnimationCurves>();
+
+		if (mRootMotion == nullptr)
+			mRootMotion = bs_shared_ptr_new<RootMotion>();
+
 		buildNameMapping();
 		calculateLength();
 	}
 
 	HAnimationClip AnimationClip::create(bool isAdditive)
 	{
-		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(_createPtr(bs_shared_ptr_new<AnimationCurves>(), isAdditive)));
+		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(
+			_createPtr(bs_shared_ptr_new<AnimationCurves>(), isAdditive)));
 	}
 
-	HAnimationClip AnimationClip::create(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate)
+	HAnimationClip AnimationClip::create(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate, 
+		const SPtr<RootMotion>& rootMotion)
 	{
-		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(_createPtr(curves, isAdditive, sampleRate)));
+		return static_resource_cast<AnimationClip>(gResources()._createResourceHandle(
+			_createPtr(curves, isAdditive, sampleRate, rootMotion)));
 	}
 
 	SPtr<AnimationClip> AnimationClip::createEmpty()
@@ -114,9 +125,10 @@ namespace BansheeEngine
 		return newClip;
 	}
 
-	SPtr<AnimationClip> AnimationClip::_createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate)
+	SPtr<AnimationClip> AnimationClip::_createPtr(const SPtr<AnimationCurves>& curves, bool isAdditive, UINT32 sampleRate, 
+		const SPtr<RootMotion>& rootMotion)
 	{
-		AnimationClip* rawPtr = new (bs_alloc<AnimationClip>()) AnimationClip(curves, isAdditive, sampleRate);
+		AnimationClip* rawPtr = new (bs_alloc<AnimationClip>()) AnimationClip(curves, isAdditive, sampleRate, rootMotion);
 
 		SPtr<AnimationClip> newClip = bs_core_ptr<AnimationClip>(rawPtr);
 		newClip->_setThisPtr(newClip);

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

@@ -27,7 +27,8 @@ namespace BansheeEngine
 
 	MeshImportOptions::MeshImportOptions()
 		: mCPUReadable(false), mImportNormals(true), mImportTangents(true), mImportBlendShapes(false), mImportSkin(false)
-		, mImportAnimation(false), mReduceKeyFrames(true), mImportScale(1.0f), mCollisionMeshType(CollisionMeshType::None)
+		, mImportAnimation(false), mReduceKeyFrames(true), mImportRootMotion(false), mImportScale(1.0f)
+		, mCollisionMeshType(CollisionMeshType::None)
 	{ }
 
 	RTTITypeBase* MeshImportOptions::getRTTIStatic()

+ 5 - 0
Source/MBansheeEditor/Inspectors/MeshInspector.cs

@@ -24,6 +24,7 @@ namespace BansheeEditor
         private GUIToggleField cpuReadableField;
         private GUIEnumField collisionMeshTypeField;
         private GUIToggleField keyFrameReductionField;
+        private GUIToggleField rootMotionField;
         private GUIArrayField<AnimationSplitInfo, AnimSplitArrayRow> animSplitInfoField;
         private GUIButton reimportButton;
 
@@ -56,6 +57,7 @@ namespace BansheeEditor
             cpuReadableField.Value = newImportOptions.CPUReadable;
             collisionMeshTypeField.Value = (ulong)newImportOptions.CollisionMeshType;
             keyFrameReductionField.Value = newImportOptions.KeyframeReduction;
+            rootMotionField.Value = newImportOptions.ImportRootMotion;
 
             importOptions = newImportOptions;
 
@@ -78,6 +80,7 @@ namespace BansheeEditor
             cpuReadableField = new GUIToggleField(new LocEdString("CPU readable"));
             collisionMeshTypeField = new GUIEnumField(typeof(CollisionMeshType), new LocEdString("Collision mesh"));
             keyFrameReductionField = new GUIToggleField(new LocEdString("Keyframe Reduction"));
+            rootMotionField = new GUIToggleField(new LocEdString("Import root motion"));
             reimportButton = new GUIButton(new LocEdString("Reimport"));
 
             normalsField.OnChanged += x => importOptions.ImportNormals = x;
@@ -89,6 +92,7 @@ namespace BansheeEditor
             cpuReadableField.OnChanged += x => importOptions.CPUReadable = x;
             collisionMeshTypeField.OnSelectionChanged += x => importOptions.CollisionMeshType = (CollisionMeshType)x;
             keyFrameReductionField.OnChanged += x => importOptions.KeyframeReduction = x;
+            rootMotionField.OnChanged += x => importOptions.ImportRootMotion = x;
 
             reimportButton.OnClick += TriggerReimport;
 
@@ -101,6 +105,7 @@ namespace BansheeEditor
             Layout.AddElement(cpuReadableField);
             Layout.AddElement(collisionMeshTypeField);
             Layout.AddElement(keyFrameReductionField);
+            Layout.AddElement(rootMotionField);
 
             splitInfos = importOptions.AnimationClipSplits;
 

+ 18 - 0
Source/MBansheeEditor/Windows/Library/ImportOptions.cs

@@ -227,6 +227,18 @@ namespace BansheeEditor
             set { Internal_SetKeyFrameReduction(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Determines if import of root motion curves is enabled. When enabled, any animation curves in imported animations 
+        /// affecting the root bone will be available through a set of separate curves in AnimationClip, and they won't be
+        /// evaluated through normal animation process. Instead it is expected that the user evaluates the curves manually
+        /// and applies them as required.
+        /// </summary>
+        public bool ImportRootMotion
+        {
+            get { return Internal_GetRootMotion(mCachedPtr); }
+            set { Internal_SetRootMotion(mCachedPtr, value); }
+        }
+
         /// <summary>
         /// Controls what type (if any) of collision mesh should be imported.
         /// </summary>
@@ -301,6 +313,12 @@ namespace BansheeEditor
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetKeyFrameReduction(IntPtr thisPtr, bool value);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern bool Internal_GetRootMotion(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetRootMotion(IntPtr thisPtr, bool value);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern AnimationSplitInfo[] Internal_GetAnimationClipSplits(IntPtr thisPtr);
 

+ 29 - 0
Source/MBansheeEngine/Animation/AnimationClip.cs

@@ -64,6 +64,16 @@ namespace BansheeEngine
             set { Internal_SetAnimationEvents(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Returns a set of curves containing motion of the root bone. This allows the user to evaluate the root bone
+        /// animation curves manually, instead of through the normal animation process. This property is only available
+        /// if animation clip was imported with root motion import enabled.
+        /// </summary>
+        public RootMotion RootMotion
+        {
+            get { return Internal_GetRootMotion(mCachedPtr); }
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_CreateInstance(AnimationClip instance);
 
@@ -79,6 +89,9 @@ namespace BansheeEngine
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetAnimationEvents(IntPtr thisPtr, AnimationEvent[] events);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern RootMotion Internal_GetRootMotion(IntPtr thisPtr);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern float Internal_GetLength(IntPtr thisPtr);
 
@@ -116,5 +129,21 @@ namespace BansheeEngine
         public float Time;
     }
 
+    /// <summary>
+    /// Contains a set of animation curves used for moving and rotating the root bone.
+    /// </summary>
+    public class RootMotion
+    {
+        /// <summary>
+        /// Animation curve representing the movement of the root bone.
+        /// </summary>
+        public Vector3Curve Position;
+
+        /// <summary>
+        /// Animation curve representing the rotation of the root bone.
+        /// </summary>
+        public QuaternionCurve Rotation;
+    }
+
     /** @} */
 }

+ 124 - 0
Source/MBansheeEngine/Animation/AnimationCurve.cs

@@ -213,4 +213,128 @@ namespace BansheeEngine
         /// </summary>
         public AnimationCurve Curve;
     }
+
+    /// <summary>
+    /// A set of animation curves for a 3D vector.
+    /// </summary>
+    public class Vector3Curve
+    {
+        /// <summary>
+        /// Constructs a new 3D vector animation curve.
+        /// </summary>
+        /// <param name="x">Curve representing the x axis of the vector.</param>
+        /// <param name="y">Curve representing the y axis of the vector.</param>
+        /// <param name="z">Curve representing the z axis of the vector.</param>
+        public Vector3Curve(AnimationCurve x, AnimationCurve y, AnimationCurve z)
+        {
+            X = x;
+            Y = y;
+            Z = z;
+        }
+
+        /// <summary>
+        /// Evaluate the animation curve at the specified time.
+        /// </summary>
+        /// <param name="time">Time to evaluate the curve at. </param>
+        /// <param name="loop">If true the curve will loop when it goes past the end or beggining. Otherwise the curve 
+        ///                    value will be clamped.</param>
+        /// <returns>Interpolated value from the curve at provided time.</returns>
+        public Vector3 Evaluate(float time, bool loop = true)
+        {
+            Vector3 output = new Vector3();
+
+            if (X != null)
+                output.x = X.Evaluate(time, loop);
+
+            if (Y != null)
+                output.y = Y.Evaluate(time, loop);
+
+            if (Z != null)
+                output.z = Z.Evaluate(time, loop);
+
+            return output;
+        }
+
+        /// <summary>
+        /// Animation curve for the x axis.
+        /// </summary>
+        public AnimationCurve X;
+
+        /// <summary>
+        /// Animation curve for the y axis.
+        /// </summary>
+        public AnimationCurve Y;
+
+        /// <summary>
+        /// Animation curve for the z axis.
+        /// </summary>
+        public AnimationCurve Z;
+    }
+
+    /// <summary>
+    /// A set of animation curves for a quaternion.
+    /// </summary>
+    public class QuaternionCurve
+    {
+        /// <summary>
+        /// Constructs a new quaternion animation curve.
+        /// </summary>
+        /// <param name="x">Curve representing the x component of the quaternion.</param>
+        /// <param name="y">Curve representing the y component of the quaternion.</param>
+        /// <param name="z">Curve representing the z component of the quaternion.</param>
+        /// <param name="w">Curve representing the w component of the quaternion.</param>
+        public QuaternionCurve(AnimationCurve x, AnimationCurve y, AnimationCurve z, AnimationCurve w)
+        {
+            X = x;
+            Y = y;
+            Z = z;
+            W = w;
+        }
+
+        /// <summary>
+        /// Evaluate the animation curve at the specified time.
+        /// </summary>
+        /// <param name="time">Time to evaluate the curve at. </param>
+        /// <param name="loop">If true the curve will loop when it goes past the end or beggining. Otherwise the curve 
+        ///                    value will be clamped.</param>
+        /// <returns>Interpolated value from the curve at provided time.</returns>
+        public Quaternion Evaluate(float time, bool loop = true)
+        {
+            Quaternion output = new Quaternion();
+
+            if (X != null)
+                output.x = X.Evaluate(time, loop);
+
+            if (Y != null)
+                output.y = Y.Evaluate(time, loop);
+
+            if (Z != null)
+                output.z = Z.Evaluate(time, loop);
+
+            if (W != null)
+                output.w = W.Evaluate(time, loop);
+
+            return output;
+        }
+
+        /// <summary>
+        /// Animation curve for the x component.
+        /// </summary>
+        public AnimationCurve X;
+
+        /// <summary>
+        /// Animation curve for the y component.
+        /// </summary>
+        public AnimationCurve Y;
+
+        /// <summary>
+        /// Animation curve for the z component.
+        /// </summary>
+        public AnimationCurve Z;
+
+        /// <summary>
+        /// Animation curve for the w component.
+        /// </summary>
+        public AnimationCurve W;
+    }
 }

+ 2 - 0
Source/SBansheeEditor/Include/BsScriptImportOptions.h

@@ -114,6 +114,8 @@ namespace BansheeEngine
 		static void internal_SetImportBlendShapes(ScriptMeshImportOptions* thisPtr, bool value);
 		static bool internal_GetKeyFrameReduction(ScriptMeshImportOptions* thisPtr);
 		static void internal_SetKeyFrameReduction(ScriptMeshImportOptions* thisPtr, bool value);
+		static bool internal_GetRootMotion(ScriptMeshImportOptions* thisPtr);
+		static void internal_SetRootMotion(ScriptMeshImportOptions* thisPtr, bool value);
 		static float internal_GetScale(ScriptMeshImportOptions* thisPtr);
 		static void internal_SetScale(ScriptMeshImportOptions* thisPtr, float value);
 		static int internal_GetCollisionMeshType(ScriptMeshImportOptions* thisPtr);

+ 12 - 0
Source/SBansheeEditor/Source/BsScriptImportOptions.cpp

@@ -180,6 +180,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetImportBlendShapes", &ScriptMeshImportOptions::internal_SetImportBlendShapes);
 		metaData.scriptClass->addInternalCall("Internal_GetKeyFrameReduction", &ScriptMeshImportOptions::internal_GetKeyFrameReduction);
 		metaData.scriptClass->addInternalCall("Internal_SetKeyFrameReduction", &ScriptMeshImportOptions::internal_SetKeyFrameReduction);
+		metaData.scriptClass->addInternalCall("Internal_GetRootMotion", &ScriptMeshImportOptions::internal_GetRootMotion);
+		metaData.scriptClass->addInternalCall("Internal_SetRootMotion", &ScriptMeshImportOptions::internal_SetRootMotion);
 		metaData.scriptClass->addInternalCall("Internal_GetScale", &ScriptMeshImportOptions::internal_GetScale);
 		metaData.scriptClass->addInternalCall("Internal_SetScale", &ScriptMeshImportOptions::internal_SetScale);
 		metaData.scriptClass->addInternalCall("Internal_GetCollisionMeshType", &ScriptMeshImportOptions::internal_GetCollisionMeshType);
@@ -284,6 +286,16 @@ namespace BansheeEngine
 		thisPtr->getMeshImportOptions()->setKeyFrameReduction(value);
 	}
 
+	bool ScriptMeshImportOptions::internal_GetRootMotion(ScriptMeshImportOptions* thisPtr)
+	{
+		return thisPtr->getMeshImportOptions()->getImportRootMotion();
+	}
+
+	void ScriptMeshImportOptions::internal_SetRootMotion(ScriptMeshImportOptions* thisPtr, bool value)
+	{
+		thisPtr->getMeshImportOptions()->setImportRootMotion(value);
+	}
+
 	float ScriptMeshImportOptions::internal_GetScale(ScriptMeshImportOptions* thisPtr)
 	{
 		return thisPtr->getMeshImportOptions()->getImportScale();

+ 1 - 0
Source/SBansheeEngine/Include/BsScriptAnimationClip.h

@@ -34,6 +34,7 @@ namespace BansheeEngine
 		static void internal_SetAnimationCurves(ScriptAnimationClip* thisPtr, MonoObject* curves);
 		static MonoArray* internal_GetAnimationEvents(ScriptAnimationClip* thisPtr);
 		static void internal_SetAnimationEvents(ScriptAnimationClip* thisPtr, MonoArray* events);
+		static MonoObject* internal_GetRootMotion(ScriptAnimationClip* thisPtr);
 		static float internal_GetLength(ScriptAnimationClip* thisPtr);
 		static UINT32 internal_GetSampleRate(ScriptAnimationClip* thisPtr);
 		static void internal_SetSampleRate(ScriptAnimationClip* thisPtr, UINT32 sampleRate);

+ 44 - 0
Source/SBansheeEngine/Include/BsScriptAnimationCurves.h

@@ -81,5 +81,49 @@ namespace BansheeEngine
 		static MonoField* sCurveField;
 	};
 
+	/**	Interop class between C++ & CLR for Vector3Curve. */
+	class BS_SCR_BE_EXPORT ScriptVector3Curve : public ScriptObject<ScriptVector3Curve>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "Vector3Curve")
+
+		/** Converts native 3D vector animation curve to its managed counterpart. */
+		static MonoObject* toManaged(const TAnimationCurve<Vector3>& curve);
+	private:
+		ScriptVector3Curve(MonoObject* instance);
+	};
+
+	/**	Interop class between C++ & CLR for QuaternionCurve. */
+	class BS_SCR_BE_EXPORT ScriptQuaternionCurve : public ScriptObject<ScriptQuaternionCurve>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "QuaternionCurve")
+
+		/** Converts native quaternion animation curve to its managed counterpart. */
+		static MonoObject* toManaged(const TAnimationCurve<Quaternion>& curve);
+	private:
+		ScriptQuaternionCurve(MonoObject* instance);
+	};
+
+	struct RootMotion;
+
+	/**	Interop class between C++ & CLR for RootMotion. */
+	class BS_SCR_BE_EXPORT ScriptRootMotion : public ScriptObject<ScriptRootMotion>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "RootMotion")
+
+		/** Converts native root motion object to its managed counterpart. */
+		static MonoObject* toManaged(const SPtr<RootMotion>& rootMotion);
+	private:
+		ScriptRootMotion(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* sPositionField;
+		static MonoField* sRotationField;
+	};
+
 	/** @} */
 }

+ 7 - 0
Source/SBansheeEngine/Source/BsScriptAnimationClip.cpp

@@ -21,6 +21,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetAnimationCurves", &ScriptAnimationClip::internal_SetAnimationCurves);
 		metaData.scriptClass->addInternalCall("Internal_GetAnimationEvents", &ScriptAnimationClip::internal_GetAnimationEvents);
 		metaData.scriptClass->addInternalCall("Internal_SetAnimationEvents", &ScriptAnimationClip::internal_SetAnimationEvents);
+		metaData.scriptClass->addInternalCall("Internal_GetRootMotion", &ScriptAnimationClip::internal_GetRootMotion);
 		metaData.scriptClass->addInternalCall("Internal_GetLength", &ScriptAnimationClip::internal_GetLength);
 		metaData.scriptClass->addInternalCall("Internal_GetSampleRate", &ScriptAnimationClip::internal_GetSampleRate);
 		metaData.scriptClass->addInternalCall("Internal_SetSampleRate", &ScriptAnimationClip::internal_SetSampleRate);
@@ -86,6 +87,12 @@ namespace BansheeEngine
 		thisPtr->getHandle()->setEvents(nativeEvents);
 	}
 
+	MonoObject* ScriptAnimationClip::internal_GetRootMotion(ScriptAnimationClip* thisPtr)
+	{
+		SPtr<RootMotion> rootMotion = thisPtr->getHandle()->getRootMotion();
+		return ScriptRootMotion::toManaged(rootMotion);
+	}
+
 	float ScriptAnimationClip::internal_GetLength(ScriptAnimationClip* thisPtr)
 	{
 		return thisPtr->getHandle()->getLength();

+ 113 - 40
Source/SBansheeEngine/Source/BsScriptAnimationCurves.cpp

@@ -151,6 +151,48 @@ namespace BansheeEngine
 		return instance;
 	}
 
+	/** Converts compound animation curves into multiple float (single value) curves. */
+	template<class T, int C>
+	void convertCurves(const TAnimationCurve<T>& curve, TAnimationCurve<float>(&output)[C])
+	{
+		UINT32 numKeyFrames = curve.getNumKeyFrames();
+		Vector<TKeyframe<float>> keyFrames[C];
+
+		for (UINT32 i = 0; i < numKeyFrames; i++)
+		{
+			const TKeyframe<T>& key = curve.getKeyFrame(i);
+
+			TKeyframe<float> newKey;
+			newKey.time = key.time;
+
+			for (UINT32 j = 0; j < C; j++)
+			{
+				bool addNew = true;
+				if (i > 0)
+				{
+					const TKeyframe<float>& prevKey = keyFrames[j].back();
+
+					bool isEqual = Math::approxEquals(prevKey.value, key.value[j]) &&
+						Math::approxEquals(prevKey.outTangent, key.inTangent[j]);
+
+					addNew = !isEqual;
+				}
+
+				if (addNew)
+				{
+					newKey.value = key.value[j];
+					newKey.inTangent = key.inTangent[j];
+					newKey.outTangent = key.outTangent[j];
+
+					keyFrames[j].push_back(newKey);
+				}
+			}
+		}
+
+		for (UINT32 i = 0; i < C; i++)
+			output[i] = TAnimationCurve<float>(keyFrames[i]);
+	}
+
 	MonoField* ScriptNamedVector3Curve::sNameField = nullptr;
 	MonoField* ScriptNamedVector3Curve::sFlagsField = nullptr;
 	MonoField* ScriptNamedVector3Curve::sXCurveField = nullptr;
@@ -247,47 +289,12 @@ namespace BansheeEngine
 	{
 		MonoString* monoString = MonoUtil::stringToMono(curve.name);
 
-		UINT32 numKeyFrames = curve.curve.getNumKeyFrames();
-		Vector<TKeyframe<float>> keyFrames[3];
-
-		for (UINT32 i = 0; i < numKeyFrames; i++)
-		{
-			const TKeyframe<Vector3>& key = curve.curve.getKeyFrame(i);
-
-			TKeyframe<float> newKey;
-			newKey.time = key.time;
-
-			for(UINT32 j = 0; j < 3; j++)
-			{
-				bool addNew = true;
-				if (i > 0)
-				{
-					const TKeyframe<float>& prevKey = keyFrames[j].back();
-
-					bool isEqual = Math::approxEquals(prevKey.value, key.value[j]) &&
-						Math::approxEquals(prevKey.outTangent, key.inTangent[j]);
-
-					addNew = !isEqual;
-				}
+		TAnimationCurve<float> curves[3];
+		convertCurves(curve.curve, curves);
 
-				if (addNew)
-				{
-					newKey.value = key.value[j];
-					newKey.inTangent = key.inTangent[j];
-					newKey.outTangent = key.outTangent[j];
-
-					keyFrames[j].push_back(newKey);
-				}
-			}
-		}
-
-		TAnimationCurve<float> xCurve(keyFrames[0]);
-		TAnimationCurve<float> yCurve(keyFrames[1]);
-		TAnimationCurve<float> zCurve(keyFrames[2]);
-
-		MonoObject* monoXCurve = ScriptAnimationCurve::create(xCurve);
-		MonoObject* monoYCurve = ScriptAnimationCurve::create(yCurve);
-		MonoObject* monoZCurve = ScriptAnimationCurve::create(zCurve);
+		MonoObject* monoXCurve = ScriptAnimationCurve::create(curves[0]);
+		MonoObject* monoYCurve = ScriptAnimationCurve::create(curves[1]);
+		MonoObject* monoZCurve = ScriptAnimationCurve::create(curves[2]);
 
 		UINT32 flags = curve.flags;
 
@@ -344,4 +351,70 @@ namespace BansheeEngine
 		void* params[3] = { monoString, &flags, monoCurve };
 		return metaData.scriptClass->createInstance("string,int,AnimationCurve", params);
 	}
+
+	ScriptVector3Curve::ScriptVector3Curve(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptVector3Curve::initRuntimeData()
+	{ }
+
+	MonoObject* ScriptVector3Curve::toManaged(const TAnimationCurve<Vector3>& curve)
+	{
+		TAnimationCurve<float> curves[3];
+		convertCurves(curve, curves);
+
+		MonoObject* monoXCurve = ScriptAnimationCurve::create(curves[0]);
+		MonoObject* monoYCurve = ScriptAnimationCurve::create(curves[1]);
+		MonoObject* monoZCurve = ScriptAnimationCurve::create(curves[2]);
+
+		void* params[3] = { monoXCurve, monoYCurve, monoZCurve };
+		return metaData.scriptClass->createInstance("AnimationCurve,AnimationCurve,AnimationCurve", params);
+	}
+
+	ScriptQuaternionCurve::ScriptQuaternionCurve(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptQuaternionCurve::initRuntimeData()
+	{ }
+
+	MonoObject* ScriptQuaternionCurve::toManaged(const TAnimationCurve<Quaternion>& curve)
+	{
+		TAnimationCurve<float> curves[4];
+		convertCurves(curve, curves);
+
+		MonoObject* monoXCurve = ScriptAnimationCurve::create(curves[0]);
+		MonoObject* monoYCurve = ScriptAnimationCurve::create(curves[1]);
+		MonoObject* monoZCurve = ScriptAnimationCurve::create(curves[2]);
+		MonoObject* monoWCurve = ScriptAnimationCurve::create(curves[3]);
+
+		void* params[4] = { monoXCurve, monoYCurve, monoZCurve, monoWCurve };
+		return metaData.scriptClass->createInstance("AnimationCurve,AnimationCurve,AnimationCurve,AnimationCurve", params);
+	}
+
+	MonoField* ScriptRootMotion::sPositionField = nullptr;
+	MonoField* ScriptRootMotion::sRotationField = nullptr;
+
+	ScriptRootMotion::ScriptRootMotion(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptRootMotion::initRuntimeData()
+	{
+		sPositionField = metaData.scriptClass->getField("Position");
+		sRotationField = metaData.scriptClass->getField("Rotation");
+	}
+
+	MonoObject* ScriptRootMotion::toManaged(const SPtr<RootMotion>& rootMotion)
+	{
+		MonoObject* monoPositionCurve = ScriptVector3Curve::toManaged(rootMotion->position);
+		MonoObject* monoRotationCurve = ScriptQuaternionCurve::toManaged(rootMotion->rotation);
+
+		MonoObject* instance = metaData.scriptClass->createInstance();
+		sPositionField->setValue(instance, monoPositionCurve);
+		sRotationField->setValue(instance, monoRotationCurve);
+
+		return instance;
+	}
 }