Browse Source

Generic script property animation

BearishSun 9 years ago
parent
commit
9fd2e0df2c

+ 16 - 0
Source/BansheeCore/Include/BsAnimation.h

@@ -195,6 +195,7 @@ namespace BansheeEngine
 		// Evaluation results
 		// Evaluation results
 		LocalSkeletonPose skeletonPose;
 		LocalSkeletonPose skeletonPose;
 		LocalSkeletonPose sceneObjectPose;
 		LocalSkeletonPose sceneObjectPose;
+		UINT32 numGenericCurves;
 		float* genericCurveOutputs;
 		float* genericCurveOutputs;
 	};
 	};
 
 
@@ -325,6 +326,19 @@ namespace BansheeEngine
 		/** Removes the curve <-> scene object mapping that was set via mapCurveToSceneObject(). */
 		/** Removes the curve <-> scene object mapping that was set via mapCurveToSceneObject(). */
 		void unmapSceneObject(const HSceneObject& so);
 		void unmapSceneObject(const HSceneObject& so);
 
 
+		/** 
+		 * Retrieves an evaluated value for a generic curve with the specified index. 
+		 *
+		 * @param[in]	curveIdx	The curve index referencing a set of curves from the first playing animation clip. 
+		 *							Generic curves from all other clips are ignored.
+		 * @param[out]	value		Value of the generic curve. Only valid if the method return true.
+		 * @return					True if the value was retrieved successfully. The method might fail if animation update
+		 *							didn't yet have a chance to execute and values are not yet available, or if the
+		 *							animation clip changed since the last frame (the last problem can be avoided by ensuring
+		 *							to read the curve values before changing the clip).
+		 */
+		bool getGenericCurveValue(UINT32 curveIdx, float& value);
+
 		/** Creates a new empty Animation object. */
 		/** Creates a new empty Animation object. */
 		static SPtr<Animation> create();
 		static SPtr<Animation> create();
 
 
@@ -381,6 +395,8 @@ namespace BansheeEngine
 		SPtr<Skeleton> mSkeleton;
 		SPtr<Skeleton> mSkeleton;
 		Vector<AnimationClipInfo> mClipInfos;
 		Vector<AnimationClipInfo> mClipInfos;
 		UnorderedMap<UINT64, AnimatedSceneObject> mSceneObjects;
 		UnorderedMap<UINT64, AnimatedSceneObject> mSceneObjects;
+		Vector<float> mGenericCurveOutputs;
+		bool mGenericCurveValuesValid;
 
 
 		// Animation thread only
 		// Animation thread only
 		SPtr<AnimationProxy> mAnimProxy;
 		SPtr<AnimationProxy> mAnimProxy;

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

@@ -50,6 +50,7 @@ namespace BansheeEngine
 		float time; /**< Time to evaluate the curve at. */
 		float time; /**< Time to evaluate the curve at. */
 		float weight; /**< Determines how much of an influence will this clip have in regard to others in the same layer. */
 		float weight; /**< Determines how much of an influence will this clip have in regard to others in the same layer. */
 		bool loop; /**< Determines should the animation loop (wrap) once ending or beginning frames are passed. */
 		bool loop; /**< Determines should the animation loop (wrap) once ending or beginning frames are passed. */
+		bool disabled; /**< If true the clip state will not be evaluated. */
 	};
 	};
 
 
 	/** Contains animation states for a single animation layer. */
 	/** Contains animation states for a single animation layer. */

+ 76 - 15
Source/BansheeCore/Source/BsAnimation.cpp

@@ -9,11 +9,11 @@
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	AnimationClipInfo::AnimationClipInfo()
 	AnimationClipInfo::AnimationClipInfo()
-		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), layerIdx(0), curveVersion(0), stateIdx(0)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), curveVersion(0), layerIdx((UINT32)-1), stateIdx((UINT32)-1)
 	{ }
 	{ }
 
 
 	AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
 	AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
-		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx(0), stateIdx(0)
+		: fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx((UINT32)-1), stateIdx((UINT32)-1)
 	{ }
 	{ }
 
 
 	Blend1DInfo::Blend1DInfo(UINT32 numClips)
 	Blend1DInfo::Blend1DInfo(UINT32 numClips)
@@ -106,6 +106,7 @@ namespace BansheeEngine
 
 
 		numLayers = 0;
 		numLayers = 0;
 		numSceneObjects = 0;
 		numSceneObjects = 0;
+		numGenericCurves = 0;
 	}
 	}
 
 
 	void AnimationProxy::rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos, 
 	void AnimationProxy::rebuild(const SPtr<Skeleton>& skeleton, Vector<AnimationClipInfo>& clipInfos, 
@@ -133,7 +134,9 @@ namespace BansheeEngine
 
 
 		bs_frame_mark();
 		bs_frame_mark();
 		{
 		{
+			FrameVector<bool> clipLoadState(clipInfos.size());
 			FrameVector<AnimationStateLayer> tempLayers;
 			FrameVector<AnimationStateLayer> tempLayers;
+			UINT32 clipIdx = 0;
 			for (auto& clipInfo : clipInfos)
 			for (auto& clipInfo : clipInfos)
 			{
 			{
 				UINT32 layer = clipInfo.state.layer;
 				UINT32 layer = clipInfo.state.layer;
@@ -148,14 +151,19 @@ namespace BansheeEngine
 					return x.index == layer;
 					return x.index == layer;
 				});
 				});
 
 
+				bool isLoaded = clipInfo.clip.isLoaded();
+				clipLoadState[clipIdx] = isLoaded;
+
 				if (iterFind == tempLayers.end())
 				if (iterFind == tempLayers.end())
 				{
 				{
 					tempLayers.push_back(AnimationStateLayer());
 					tempLayers.push_back(AnimationStateLayer());
 					AnimationStateLayer& newLayer = tempLayers.back();
 					AnimationStateLayer& newLayer = tempLayers.back();
 
 
 					newLayer.index = layer;
 					newLayer.index = layer;
-					newLayer.additive = clipInfo.clip.isLoaded() && clipInfo.clip->isAdditive();
+					newLayer.additive = isLoaded && clipInfo.clip->isAdditive();
 				}
 				}
+
+				clipIdx++;
 			}
 			}
 
 
 			std::sort(tempLayers.begin(), tempLayers.end(), 
 			std::sort(tempLayers.begin(), tempLayers.end(), 
@@ -176,18 +184,25 @@ namespace BansheeEngine
 			UINT32 numPosCurves = 0;
 			UINT32 numPosCurves = 0;
 			UINT32 numRotCurves = 0;
 			UINT32 numRotCurves = 0;
 			UINT32 numScaleCurves = 0;
 			UINT32 numScaleCurves = 0;
-			UINT32 numGenCurves = 0;
 
 
+			clipIdx = 0;
 			for (auto& clipInfo : clipInfos)
 			for (auto& clipInfo : clipInfos)
 			{
 			{
-				if (!clipInfo.clip.isLoaded())
+				bool isLoaded = clipLoadState[clipIdx++];
+				if (!isLoaded)
 					continue;
 					continue;
 
 
 				SPtr<AnimationCurves> curves = clipInfo.clip->getCurves();
 				SPtr<AnimationCurves> curves = clipInfo.clip->getCurves();
 				numPosCurves += (UINT32)curves->position.size();
 				numPosCurves += (UINT32)curves->position.size();
 				numRotCurves += (UINT32)curves->rotation.size();
 				numRotCurves += (UINT32)curves->rotation.size();
 				numScaleCurves += (UINT32)curves->scale.size();
 				numScaleCurves += (UINT32)curves->scale.size();
-				numGenCurves += (UINT32)curves->generic.size();
+			}
+
+			numGenericCurves = 0;
+			if(clipInfos.size() > 0 && clipLoadState[0])
+			{
+				SPtr<AnimationCurves> curves = clipInfos[0].clip->getCurves();
+				numGenericCurves = (UINT32)curves->generic.size();
 			}
 			}
 
 
 			UINT32* mappedBoneIndices = (UINT32*)bs_frame_alloc(sizeof(UINT32) * numSceneObjects);
 			UINT32* mappedBoneIndices = (UINT32*)bs_frame_alloc(sizeof(UINT32) * numSceneObjects);
@@ -222,8 +237,8 @@ namespace BansheeEngine
 			UINT32 posCacheSize = numPosCurves * sizeof(TCurveCache<Vector3>);
 			UINT32 posCacheSize = numPosCurves * sizeof(TCurveCache<Vector3>);
 			UINT32 rotCacheSize = numRotCurves * sizeof(TCurveCache<Quaternion>);
 			UINT32 rotCacheSize = numRotCurves * sizeof(TCurveCache<Quaternion>);
 			UINT32 scaleCacheSize = numScaleCurves * sizeof(TCurveCache<Vector3>);
 			UINT32 scaleCacheSize = numScaleCurves * sizeof(TCurveCache<Vector3>);
-			UINT32 genCacheSize = numGenCurves * sizeof(TCurveCache<float>);
-			UINT32 genericCurveOutputSize = numGenCurves * sizeof(float);
+			UINT32 genCacheSize = numGenericCurves * sizeof(TCurveCache<float>);
+			UINT32 genericCurveOutputSize = numGenericCurves * sizeof(float);
 			UINT32 sceneObjectIdsSize = numSceneObjects * sizeof(AnimatedSceneObjectInfo);
 			UINT32 sceneObjectIdsSize = numSceneObjects * sizeof(AnimatedSceneObjectInfo);
 			UINT32 sceneObjectTransformsSize = numBoneMappedSOs * sizeof(Matrix4);
 			UINT32 sceneObjectTransformsSize = numBoneMappedSOs * sizeof(Matrix4);
 
 
@@ -265,7 +280,7 @@ namespace BansheeEngine
 			data += scaleCacheSize;
 			data += scaleCacheSize;
 
 
 			TCurveCache<float>* genCache = (TCurveCache<float>*)data;
 			TCurveCache<float>* genCache = (TCurveCache<float>*)data;
-			for (UINT32 i = 0; i < numGenCurves; i++)
+			for (UINT32 i = 0; i < numGenericCurves; i++)
 				new (&genCache[i]) TCurveCache<float>();
 				new (&genCache[i]) TCurveCache<float>();
 
 
 			data += genCacheSize;
 			data += genCacheSize;
@@ -285,6 +300,8 @@ namespace BansheeEngine
 			UINT32 curLayerIdx = 0;
 			UINT32 curLayerIdx = 0;
 			UINT32 curStateIdx = 0;
 			UINT32 curStateIdx = 0;
 
 
+			// Note: Hidden dependency. First clip info must be in layers[0].states[0] (needed for generic curves which only
+			// use the primary clip).
 			for(UINT32 i = 0; i < numLayers; i++)
 			for(UINT32 i = 0; i < numLayers; i++)
 			{
 			{
 				AnimationStateLayer& layer = layers[i];
 				AnimationStateLayer& layer = layers[i];
@@ -293,8 +310,10 @@ namespace BansheeEngine
 				layer.numStates = 0;
 				layer.numStates = 0;
 
 
 				UINT32 localStateIdx = 0;
 				UINT32 localStateIdx = 0;
-				for(auto& clipInfo : clipInfos)
+				for(UINT32 j = 0; j < (UINT32)clipInfos.size(); j++)
 				{
 				{
+					AnimationClipInfo& clipInfo = clipInfos[j];
+
 					UINT32 clipLayer = clipInfo.state.layer;
 					UINT32 clipLayer = clipInfo.state.layer;
 					if (clipLayer == (UINT32)-1)
 					if (clipLayer == (UINT32)-1)
 						clipLayer = 0;
 						clipLayer = 0;
@@ -326,13 +345,17 @@ namespace BansheeEngine
 					state.weight = weight;
 					state.weight = weight;
 
 
 					// Set up individual curves and their caches
 					// Set up individual curves and their caches
-					bool isClipValid = clipInfo.clip.isLoaded();
-					if(isClipValid)
+					bool isClipValid = clipLoadState[j];
+					if (isClipValid)
+					{
 						state.curves = clipInfo.clip->getCurves();
 						state.curves = clipInfo.clip->getCurves();
+						state.disabled = false;
+					}
 					else
 					else
 					{
 					{
 						static SPtr<AnimationCurves> zeroCurves = bs_shared_ptr_new<AnimationCurves>();
 						static SPtr<AnimationCurves> zeroCurves = bs_shared_ptr_new<AnimationCurves>();
 						state.curves = zeroCurves;
 						state.curves = zeroCurves;
+						state.disabled = true;
 					}
 					}
 
 
 					state.positionCaches = posCache;
 					state.positionCaches = posCache;
@@ -405,12 +428,14 @@ namespace BansheeEngine
 
 
 					if (isSOValid)
 					if (isSOValid)
 					{
 					{
-						for (auto& clipInfo : clipInfos)
+						for (UINT32 j = 0; j < (UINT32)clipInfos.size(); j++)
 						{
 						{
+							AnimationClipInfo& clipInfo = clipInfos[j];
+
 							soInfo.layerIdx = clipInfo.layerIdx;
 							soInfo.layerIdx = clipInfo.layerIdx;
 							soInfo.stateIdx = clipInfo.stateIdx;
 							soInfo.stateIdx = clipInfo.stateIdx;
 
 
-							bool isClipValid = clipInfo.clip.isLoaded();
+							bool isClipValid = clipLoadState[j];
 							if (isClipValid)
 							if (isClipValid)
 							{
 							{
 								// Note: If there are multiple clips with the relevant curve name, we only use the first
 								// Note: If there are multiple clips with the relevant curve name, we only use the first
@@ -479,6 +504,7 @@ namespace BansheeEngine
 
 
 	Animation::Animation()
 	Animation::Animation()
 		: mDefaultWrapMode(AnimWrapMode::Loop), mDefaultSpeed(1.0f), mDirty(AnimDirtyStateFlag::Skeleton)
 		: mDefaultWrapMode(AnimWrapMode::Loop), mDefaultSpeed(1.0f), mDirty(AnimDirtyStateFlag::Skeleton)
+		, mGenericCurveValuesValid(false)
 	{
 	{
 		mId = AnimationManager::instance().registerAnimation(this);
 		mId = AnimationManager::instance().registerAnimation(this);
 		mAnimProxy = bs_shared_ptr_new<AnimationProxy>(mId);
 		mAnimProxy = bs_shared_ptr_new<AnimationProxy>(mId);
@@ -919,6 +945,15 @@ namespace BansheeEngine
 		mDirty |= AnimDirtyStateFlag::Skeleton;
 		mDirty |= AnimDirtyStateFlag::Skeleton;
 	}
 	}
 
 
+	bool Animation::getGenericCurveValue(UINT32 curveIdx, float& value)
+	{
+		if (!mGenericCurveValuesValid || curveIdx >= (UINT32)mGenericCurveOutputs.size())
+			return false;
+
+		value = mGenericCurveOutputs[curveIdx];
+		return true;
+	}
+
 	SPtr<Animation> Animation::create()
 	SPtr<Animation> Animation::create()
 	{
 	{
 		Animation* anim = new (bs_alloc<Animation>()) Animation();
 		Animation* anim = new (bs_alloc<Animation>()) Animation();
@@ -1035,6 +1070,32 @@ namespace BansheeEngine
 			}
 			}
 		}
 		}
 
 
-		// TODO - Copy generic animation data and add a method for retrieving it
+		// Must ensure that clip in the proxy and current primary clip are the same
+		mGenericCurveValuesValid = false;
+		if(mAnimProxy->numLayers > 0 || mAnimProxy->layers[0].numStates > 0)
+		{
+			const AnimationState& state = mAnimProxy->layers[0].states[0];
+
+			if(!state.disabled && mClipInfos.size() > 0)
+			{
+				const AnimationClipInfo& clipInfo = mClipInfos[0];
+
+				if (clipInfo.stateIdx == 0 && clipInfo.layerIdx == 0)
+				{
+					if (clipInfo.clip.isLoaded() && clipInfo.curveVersion == clipInfo.clip->getVersion())
+					{
+						UINT32 numGenericCurves = (UINT32)clipInfo.clip->getCurves()->generic.size();
+						mGenericCurveValuesValid = numGenericCurves == mAnimProxy->numGenericCurves;
+					}
+				}
+			}
+		}
+
+		if(mGenericCurveValuesValid)
+		{
+			mGenericCurveOutputs.resize(mAnimProxy->numGenericCurves);
+
+			memcpy(mGenericCurveOutputs.data(), mAnimProxy->genericCurveOutputs, mAnimProxy->numGenericCurves * sizeof(float));
+		}
 	}
 	}
 }
 }

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

@@ -149,6 +149,8 @@ namespace BansheeEngine
 					continue;
 					continue;
 
 
 				const AnimationState& state = anim->layers[soInfo.layerIdx].states[soInfo.stateIdx];
 				const AnimationState& state = anim->layers[soInfo.layerIdx].states[soInfo.stateIdx];
+				if (state.disabled)
+					continue;
 
 
 				{
 				{
 					UINT32 curveIdx = soInfo.curveIndices.position;
 					UINT32 curveIdx = soInfo.curveIndices.position;
@@ -188,6 +190,8 @@ namespace BansheeEngine
 			if (anim->numLayers > 0 && anim->layers[0].numStates > 0)
 			if (anim->numLayers > 0 && anim->layers[0].numStates > 0)
 			{
 			{
 				const AnimationState& state = anim->layers[0].states[0];
 				const AnimationState& state = anim->layers[0].states[0];
+				if (state.disabled)
+					continue;
 
 
 				{
 				{
 					UINT32 numCurves = (UINT32)state.curves->generic.size();
 					UINT32 numCurves = (UINT32)state.curves->generic.size();

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

@@ -129,6 +129,7 @@ namespace BansheeEngine
 			state.rotationCaches = rotationCache.data();
 			state.rotationCaches = rotationCache.data();
 			state.scaleCaches = scaleCache.data();
 			state.scaleCaches = scaleCache.data();
 			state.genericCaches = nullptr;
 			state.genericCaches = nullptr;
+			state.disabled = false;
 
 
 			AnimationStateLayer layer;
 			AnimationStateLayer layer;
 			layer.index = 0;
 			layer.index = 0;
@@ -176,6 +177,8 @@ namespace BansheeEngine
 			for (UINT32 j = 0; j < layer.numStates; j++)
 			for (UINT32 j = 0; j < layer.numStates; j++)
 			{
 			{
 				const AnimationState& state = layer.states[j];
 				const AnimationState& state = layer.states[j];
+				if (state.disabled)
+					continue;
 
 
 				float normWeight = state.weight * invLayerWeight;
 				float normWeight = state.weight * invLayerWeight;
 
 

+ 29 - 130
Source/MBansheeEditor/Windows/Animation/GUIAnimFieldDisplay.cs

@@ -110,120 +110,6 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
-        public static SerializableProperty FindProperty(SceneObject root, string path)
-        {
-            if (string.IsNullOrEmpty(path) || root == null)
-                return null;
-
-            string trimmedPath = path.Trim('/');
-            string[] entries = trimmedPath.Split('/');
-
-            // Find scene object referenced by the path
-            SceneObject so = null;
-            int pathIdx = 0;
-            for (; pathIdx < entries.Length; pathIdx++)
-            {
-                string entry = entries[pathIdx];
-
-                if (string.IsNullOrEmpty(entry))
-                    continue;
-
-                // Not a scene object, break
-                if (entry[0] != '!')
-                    break;
-
-                if (so == null)
-                    so = root;
-                else
-                {
-                    string childName = entry.Substring(1, entry.Length - 1);
-                    so = so.FindChild(childName);
-
-                    if (so == null)
-                        break;
-                }
-            }
-
-            // Cannot find scene object with specified hierarchy & name
-            if (so == null)
-                return null;
-
-            if (pathIdx >= entries.Length)
-                return null;
-
-            // If path is referencing a component, find it
-            Component component = null;
-            {
-                string entry = entries[pathIdx];
-                if (entry[0] == ':')
-                {
-                    string componentName = entry.Substring(1, entry.Length - 1);
-
-                    Component[] components = so.GetComponents();
-                    component = Array.Find(components, x => x.GetType().Name == componentName);
-
-                    // Cannot find component with specified type
-                    if (component == null)
-                        return null;
-                }
-            }
-
-            // Look for a field within a component
-            if (component != null)
-            {
-                pathIdx++;
-                if (pathIdx >= entries.Length)
-                    return null;
-
-                SerializableObject componentObj = new SerializableObject(component);
-
-                StringBuilder pathBuilder = new StringBuilder();
-                for(; pathIdx < entries.Length; pathIdx++)
-                    pathBuilder.Append(entries[pathIdx] + "/");
-
-                return componentObj.FindProperty(pathBuilder.ToString());
-            }
-            else // Field is one of the builtin ones on the SceneObject itself
-            {
-                if ((pathIdx + 1) < entries.Length)
-                    return null;
-
-                string entry = entries[pathIdx];
-                if (entry == "Position")
-                {
-                    SerializableProperty property = new SerializableProperty(
-                        SerializableProperty.FieldType.Vector3,
-                        typeof(Vector3),
-                        () => so.LocalPosition, 
-                        (x) => so.LocalPosition = (Vector3)x);
-
-                    return property;
-                }
-                else if (entry == "Rotation")
-                {
-                    SerializableProperty property = new SerializableProperty(
-                        SerializableProperty.FieldType.Vector3,
-                        typeof(Vector3),
-                        () => so.LocalRotation.ToEuler(), 
-                        (x) => so.LocalRotation = Quaternion.FromEuler((Vector3)x));
-
-                    return property;
-                }
-                else if (entry == "Scale")
-                {
-                    SerializableProperty property = new SerializableProperty(
-                        SerializableProperty.FieldType.Vector3,
-                        typeof(Vector3),
-                        () => so.LocalScale, 
-                        (x) => so.LocalScale = (Vector3)x);
-
-                    return property;
-                }
-
-                return null;
-            }
-        }
-
         private void Rebuild()
         private void Rebuild()
         {
         {
             scrollArea.Layout.Clear();
             scrollArea.Layout.Clear();
@@ -258,7 +144,12 @@ namespace BansheeEditor
             fields = new GUIAnimFieldEntry[paths.Count];
             fields = new GUIAnimFieldEntry[paths.Count];
             for (int i = 0; i < paths.Count; i++)
             for (int i = 0; i < paths.Count; i++)
             {
             {
-                SerializableProperty property = FindProperty(root, paths[i]);
+                if (string.IsNullOrEmpty(paths[i]))
+                    continue;
+
+                string pathSuffix;
+                SerializableProperty property = Animation.FindProperty(root, paths[i], out pathSuffix);
+
                 if (property != null)
                 if (property != null)
                 {
                 {
                     switch (property.Type)
                     switch (property.Type)
@@ -395,7 +286,7 @@ namespace BansheeEditor
             string trimmedPath = path.Trim('/');
             string trimmedPath = path.Trim('/');
             GetNames(trimmedPath, shortName, out soName, out compName, out propertyPath);
             GetNames(trimmedPath, shortName, out soName, out compName, out propertyPath);
 
 
-            if (soName == null || propertyPath == null)
+            if (propertyPath == null)
                 return "";
                 return "";
 
 
             if (shortName)
             if (shortName)
@@ -408,10 +299,20 @@ namespace BansheeEditor
                 else
                 else
                     truncatedPropPath = propertyPath;
                     truncatedPropPath = propertyPath;
 
 
-                if (compName != null)
-                    return soName + "(" + compName + ") - " + truncatedPropPath;
+                if (soName != null)
+                {
+                    if (compName != null)
+                        return soName + "(" + compName + ") - " + truncatedPropPath;
+                    else
+                        return soName + " - " + truncatedPropPath;
+                }
                 else
                 else
-                    return soName + " - " + truncatedPropPath;
+                {
+                    if (compName != null)
+                        return "(" + compName + ") - " + truncatedPropPath;
+                    else
+                        return truncatedPropPath;
+                }
             }
             }
         }
         }
 
 
@@ -421,6 +322,7 @@ namespace BansheeEditor
 
 
             // Find name of the last scene object in the path
             // Find name of the last scene object in the path
             int pathIdx = 0;
             int pathIdx = 0;
+            soName = null;
             for (; pathIdx < entries.Length; pathIdx++)
             for (; pathIdx < entries.Length; pathIdx++)
             {
             {
                 string entry = entries[pathIdx];
                 string entry = entries[pathIdx];
@@ -430,20 +332,17 @@ namespace BansheeEditor
 
 
                 // Not a scene object, break
                 // Not a scene object, break
                 if (entry[0] != '!')
                 if (entry[0] != '!')
-                    break;
-            }
-
-            if (pathIdx == 0)
-            {
-                soName = null;
-                compName = null;
-                propertyPath = null;
+                {
+                    if (pathIdx > 0)
+                    {
+                        string prevEntry = entries[pathIdx - 1];
+                        soName = prevEntry.Substring(1, prevEntry.Length - 1);
+                    }
 
 
-                return;
+                    break;
+                }
             }
             }
 
 
-            soName = entries[pathIdx - 1].Substring(1, entries[pathIdx - 1].Length - 1);
-
             if (pathIdx >= entries.Length)
             if (pathIdx >= entries.Length)
             {
             {
                 compName = null;
                 compName = null;

+ 1 - 1
Source/MBansheeEditor/Windows/Animation/GUIFieldSelector.cs

@@ -294,7 +294,7 @@ namespace BansheeEditor
                 parent.children = new Element[3];
                 parent.children = new Element[3];
 
 
                 parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position", SerializableProperty.FieldType.Vector3);
                 parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position", SerializableProperty.FieldType.Vector3);
-                parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation", SerializableProperty.FieldType.Vector4);
+                parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation", SerializableProperty.FieldType.Vector3);
                 parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale", SerializableProperty.FieldType.Vector3);
                 parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale", SerializableProperty.FieldType.Vector3);
             }
             }
         }
         }

+ 15 - 16
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -581,16 +581,6 @@ namespace BansheeEditor
             loadVector3Curve(clipCurves.ScaleCurves, editorCurveData.scaleCurves, "/Scale");
             loadVector3Curve(clipCurves.ScaleCurves, editorCurveData.scaleCurves, "/Scale");
 
 
             // Find which individual float curves belong to the same field
             // Find which individual float curves belong to the same field
-            Dictionary<string, Tuple<int, bool>> suffixToIdxMapping = new Dictionary<string, Tuple<int, bool>>();
-            suffixToIdxMapping[".x"] = Tuple.Create(0, true);
-            suffixToIdxMapping[".y"] = Tuple.Create(1, true);
-            suffixToIdxMapping[".z"] = Tuple.Create(2, true);
-            suffixToIdxMapping[".w"] = Tuple.Create(3, true);
-            suffixToIdxMapping[".r"] = Tuple.Create(0, false);
-            suffixToIdxMapping[".g"] = Tuple.Create(1, false);
-            suffixToIdxMapping[".b"] = Tuple.Create(2, false);
-            suffixToIdxMapping[".a"] = Tuple.Create(3, false);
-
             Dictionary<string, Tuple<int, int, bool>[]> floatCurveMapping = new Dictionary<string, Tuple<int, int, bool>[]>();
             Dictionary<string, Tuple<int, int, bool>[]> floatCurveMapping = new Dictionary<string, Tuple<int, int, bool>[]>();
             {
             {
                 int curveIdx = 0;
                 int curveIdx = 0;
@@ -621,20 +611,20 @@ namespace BansheeEditor
                         currentTangentIdx++;
                         currentTangentIdx++;
                     }
                     }
 
 
-                    Tuple<int, bool> suffixInfo;
-                    if (suffixToIdxMapping.TryGetValue(pathSuffix, out suffixInfo))
+                    Animation.PropertySuffixInfo suffixInfo;
+                    if (Animation.PropertySuffixInfos.TryGetValue(pathSuffix, out suffixInfo))
                     {
                     {
                         Tuple<int, int, bool>[] curveInfo;
                         Tuple<int, int, bool>[] curveInfo;
                         if (!floatCurveMapping.TryGetValue(pathNoSuffix, out curveInfo))
                         if (!floatCurveMapping.TryGetValue(pathNoSuffix, out curveInfo))
                             curveInfo = new Tuple<int, int, bool>[4];
                             curveInfo = new Tuple<int, int, bool>[4];
 
 
-                        curveInfo[suffixInfo.Item1] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.Item2);
+                        curveInfo[suffixInfo.elementIdx] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector);
                         floatCurveMapping[pathNoSuffix] = curveInfo;
                         floatCurveMapping[pathNoSuffix] = curveInfo;
                     }
                     }
                     else
                     else
                     {
                     {
                         Tuple<int, int, bool>[] curveInfo = new Tuple<int, int, bool>[4];
                         Tuple<int, int, bool>[] curveInfo = new Tuple<int, int, bool>[4];
-                        curveInfo[0] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.Item2);
+                        curveInfo[0] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector);
 
 
                         floatCurveMapping[path] = curveInfo;
                         floatCurveMapping[path] = curveInfo;
                     }
                     }
@@ -921,7 +911,8 @@ namespace BansheeEditor
             List<GUIAnimFieldPathValue> values = new List<GUIAnimFieldPathValue>();
             List<GUIAnimFieldPathValue> values = new List<GUIAnimFieldPathValue>();
             foreach (var kvp in curves)
             foreach (var kvp in curves)
             {
             {
-                SerializableProperty property = GUIAnimFieldDisplay.FindProperty(selectedSO, kvp.Key);
+                string suffix;
+                SerializableProperty property = Animation.FindProperty(selectedSO, kvp.Key, out suffix);
                 if (property != null)
                 if (property != null)
                 {
                 {
                     GUIAnimFieldPathValue fieldValue = new GUIAnimFieldPathValue();
                     GUIAnimFieldPathValue fieldValue = new GUIAnimFieldPathValue();
@@ -1348,7 +1339,15 @@ namespace BansheeEditor
         #region General callbacks
         #region General callbacks
         private void OnFieldAdded(string path, SerializableProperty.FieldType type)
         private void OnFieldAdded(string path, SerializableProperty.FieldType type)
         {
         {
-            AddNewField(path, type);
+            // Remove the root scene object from the path (we know which SO it is, no need to hardcode its name in the path)
+            string pathNoRoot = path.TrimStart('/');
+            int separatorIdx = pathNoRoot.IndexOf("/");
+            if (separatorIdx == -1 || (separatorIdx + 1) >= pathNoRoot.Length)
+                return;
+
+            pathNoRoot = pathNoRoot.Substring(separatorIdx + 1, pathNoRoot.Length - separatorIdx - 1);
+
+            AddNewField(pathNoRoot, type);
         }
         }
 
 
         private void OnHorzScrollOrResize(float position, float size)
         private void OnHorzScrollOrResize(float position, float size)

+ 346 - 3
Source/MBansheeEngine/Animation/Animation.cs

@@ -1,7 +1,9 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 using System;
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using System.Text;
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -104,8 +106,25 @@ namespace BansheeEngine
     {
     {
         private NativeAnimation _native;
         private NativeAnimation _native;
 
 
-        [SerializeField]
-        private SerializableData serializableData = new SerializableData();
+        [SerializeField] private SerializableData serializableData = new SerializableData();
+
+        private FloatCurvePropertyInfo[] floatProperties;
+
+        /// <summary>
+        /// Contains mapping for a suffix used by property paths used for curve identifiers, to their index and type.
+        /// </summary>
+        internal static readonly Dictionary<string, PropertySuffixInfo> PropertySuffixInfos = new Dictionary
+            <string, PropertySuffixInfo>
+        {
+            {".x", new PropertySuffixInfo(0, true)},
+            {".y", new PropertySuffixInfo(1, true)},
+            {".z", new PropertySuffixInfo(2, true)},
+            {".w", new PropertySuffixInfo(3, true)},
+            {".r", new PropertySuffixInfo(0, false)},
+            {".g", new PropertySuffixInfo(1, false)},
+            {".b", new PropertySuffixInfo(2, false)},
+            {".a", new PropertySuffixInfo(3, false)}
+        };
 
 
         /// <summary>
         /// <summary>
         /// Returns the non-component version of Animation that is wrapped by this component. 
         /// Returns the non-component version of Animation that is wrapped by this component. 
@@ -129,7 +148,10 @@ namespace BansheeEngine
                 serializableData.defaultClip = value;
                 serializableData.defaultClip = value;
 
 
                 if (value != null && _native != null)
                 if (value != null && _native != null)
+                {
+                    RebuildFloatProperties(value);
                     _native.Play(value);
                     _native.Play(value);
+                }
             }
             }
         }
         }
 
 
@@ -185,8 +207,11 @@ namespace BansheeEngine
         /// <param name="clip">Clip to play.</param>
         /// <param name="clip">Clip to play.</param>
         public void Play(AnimationClip clip)
         public void Play(AnimationClip clip)
         {
         {
-            if(_native != null)
+            if (_native != null)
+            {
+                RebuildFloatProperties(clip);
                 _native.Play(clip);
                 _native.Play(clip);
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -285,6 +310,158 @@ namespace BansheeEngine
             return false;
             return false;
         }
         }
 
 
+        /// <summary>
+        /// Searches the scene object hierarchy to find a property at the given path.
+        /// </summary>
+        /// <param name="root">Root scene object to which the path is relative to.</param>
+        /// <param name="path">Path to the property, where each element of the path is separated with "/". 
+        /// 
+        ///                    Path elements prefixed with "!" signify names of child scene objects (first one relative to 
+        ///                    <paramref name="root"/>. Name of the root element should not be included in the path. 
+        /// 
+        ///                    Path element prefixed with ":" signify names of components. If a path doesn't have a
+        ///                    component element, it is assumed the field is relative to the scene object itself (only 
+        ///                    "Translation", "Rotation" and "Scale fields are supported in such case). Only one component
+        ///                    path element per path is allowed.
+        /// 
+        ///                    Path entries with no prefix are considered regular script object fields. Each path must have
+        ///                    at least one such entry. Last field entry can optionally have a suffix separated from the
+        ///                    path name with ".". This suffix is not parsed internally, but will be returned as 
+        ///                    <paramref name="suffix"/>.
+        /// 
+        ///                    Path examples:
+        ///                     :MyComponent/myInt (path to myInt variable on a component attached to this object)
+        ///                     !childSO/:MyComponent/myInt (path to myInt variable on a child scene object)
+        ///                     !childSO/Translation (path to the scene object translation)
+        ///                     :MyComponent/myVector.z (path to the z component of myVector on this object)
+        ///                    </param>
+        /// <param name="suffix">Suffix of the last field entry, if it has any. Contains the suffix separator ".".</param>
+        /// <returns>If found, property object you can use for setting and getting the value from the property, otherwise 
+        ///          null.</returns>
+        internal static SerializableProperty FindProperty(SceneObject root, string path, out string suffix)
+        {
+            suffix = null;
+
+            if (string.IsNullOrEmpty(path) || root == null)
+                return null;
+
+            string trimmedPath = path.Trim('/');
+            string[] entries = trimmedPath.Split('/');
+
+            // Find scene object referenced by the path
+            SceneObject so = root;
+            int pathIdx = 0;
+            for (; pathIdx < entries.Length; pathIdx++)
+            {
+                string entry = entries[pathIdx];
+
+                if (string.IsNullOrEmpty(entry))
+                    continue;
+
+                // Not a scene object, break
+                if (entry[0] != '!')
+                    break;
+
+                string childName = entry.Substring(1, entry.Length - 1);
+                so = so.FindChild(childName);
+
+                if (so == null)
+                    break;
+            }
+
+            // Child scene object couldn't be found
+            if (so == null)
+                return null;
+
+            // Path too short, no field entry
+            if (pathIdx >= entries.Length)
+                return null;
+
+            // Check if path is referencing a component, and if so find it
+            Component component = null;
+            {
+                string entry = entries[pathIdx];
+                if (entry[0] == ':')
+                {
+                    string componentName = entry.Substring(1, entry.Length - 1);
+
+                    Component[] components = so.GetComponents();
+                    component = Array.Find(components, x => x.GetType().Name == componentName);
+
+                    // Cannot find component with specified type
+                    if (component == null)
+                        return null;
+                }
+            }
+
+            // Look for a field within a component
+            if (component != null)
+            {
+                pathIdx++;
+                if (pathIdx >= entries.Length)
+                    return null;
+
+                SerializableObject componentObj = new SerializableObject(component);
+
+                StringBuilder pathBuilder = new StringBuilder();
+                for (; pathIdx < entries.Length - 1; pathIdx++)
+                    pathBuilder.Append(entries[pathIdx] + "/");
+
+                // Check last path entry for suffix and remove it
+                int suffixIdx = entries[pathIdx].LastIndexOf(".");
+                if (suffixIdx != -1)
+                {
+                    string entryNoSuffix = entries[pathIdx].Substring(0, suffixIdx);
+                    suffix = entries[pathIdx].Substring(suffixIdx, entries[pathIdx].Length - suffixIdx);
+
+                    pathBuilder.Append(entryNoSuffix);
+                }
+                else
+                    pathBuilder.Append(entries[pathIdx]);
+
+                return componentObj.FindProperty(pathBuilder.ToString());
+            }
+            else // Field is one of the builtin ones on the SceneObject itself
+            {
+                if ((pathIdx + 1) < entries.Length)
+                    return null;
+
+                string entry = entries[pathIdx];
+                if (entry == "Position")
+                {
+                    SerializableProperty property = new SerializableProperty(
+                        SerializableProperty.FieldType.Vector3,
+                        typeof(Vector3),
+                        () => so.LocalPosition,
+                        (x) => so.LocalPosition = (Vector3) x);
+
+                    return property;
+                }
+                else if (entry == "Rotation")
+                {
+                    SerializableProperty property = new SerializableProperty(
+                        SerializableProperty.FieldType.Vector3,
+                        typeof(Vector3),
+                        () => so.LocalRotation.ToEuler(),
+                        (x) => so.LocalRotation = Quaternion.FromEuler((Vector3) x));
+
+                    return property;
+                }
+                else if (entry == "Scale")
+                {
+                    SerializableProperty property = new SerializableProperty(
+                        SerializableProperty.FieldType.Vector3,
+                        typeof(Vector3),
+                        () => so.LocalScale,
+                        (x) => so.LocalScale = (Vector3) x);
+
+                    return property;
+                }
+
+                return null;
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Changes the state of a playing animation clip. If animation clip is not currently playing the state change is
         /// Changes the state of a playing animation clip. If animation clip is not currently playing the state change is
         /// ignored.
         /// ignored.
@@ -297,6 +474,22 @@ namespace BansheeEngine
                 _native.SetState(clip, state);
                 _native.SetState(clip, state);
         }
         }
 
 
+        private void OnUpdate()
+        {
+            // TODO: There could currently be a mismatch between the serialized properties and the active animation clip
+            //  - Add PrimaryClip field to NativeAnimation, then compare to the active clip and update if needed
+            // TODO: If primary animation clip isn't playing, don't update float properties
+            //  - Add dirty flags to each curve value and don't update unless they changed
+
+            // Apply values from generic float curves 
+            foreach (var entry in floatProperties)
+            {
+                float curveValue;
+                if (_native.GetGenericCurveValue(entry.curveIdx, out curveValue))
+                    entry.setter(curveValue);
+            }
+        }
+
         private void OnEnable()
         private void OnEnable()
         {
         {
             RestoreNative();
             RestoreNative();
@@ -330,6 +523,8 @@ namespace BansheeEngine
             if (serializableData.defaultClip != null)
             if (serializableData.defaultClip != null)
                 _native.Play(serializableData.defaultClip);
                 _native.Play(serializableData.defaultClip);
 
 
+            RebuildFloatProperties(serializableData.defaultClip);
+
             Renderable renderable = SceneObject.GetComponent<Renderable>();
             Renderable renderable = SceneObject.GetComponent<Renderable>();
             if (renderable == null)
             if (renderable == null)
                 return;
                 return;
@@ -360,6 +555,130 @@ namespace BansheeEngine
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Builds a list of properties that will be animated using float animation curves.
+        /// </summary>
+        /// <param name="clip">Clip to retrieve the float animation curves from.</param>
+        private void RebuildFloatProperties(AnimationClip clip)
+        {
+            if (clip == null)
+            {
+                floatProperties = null;
+                return;
+            }
+
+            AnimationCurves curves = clip.Curves;
+
+            List<FloatCurvePropertyInfo> newFloatProperties = new List<FloatCurvePropertyInfo>();
+            for (int i = 0; i < curves.FloatCurves.Length; i++)
+            {
+                string suffix;
+                SerializableProperty property = FindProperty(SceneObject, curves.FloatCurves[i].Name, out suffix);
+                if (property == null)
+                    continue;
+
+                int elementIdx = 0;
+                if (!string.IsNullOrEmpty(suffix))
+                {
+                    PropertySuffixInfo suffixInfo;
+                    if (PropertySuffixInfos.TryGetValue(suffix, out suffixInfo))
+                        elementIdx = suffixInfo.elementIdx;
+                }
+
+                Action<float> setter = null;
+
+                Type internalType = property.InternalType;
+                switch (property.Type)
+                {
+                    case SerializableProperty.FieldType.Vector2:
+                        if (internalType == typeof(Vector2))
+                        {
+                            setter = f =>
+                            {
+                                Vector2 value = property.GetValue<Vector2>();
+                                value[elementIdx] = f;
+                                property.SetValue(value);
+                            };
+                        }
+
+                        break;
+                    case SerializableProperty.FieldType.Vector3:
+                        if (internalType == typeof(Vector3))
+                        {
+                            setter = f =>
+                            {
+                                Vector3 value = property.GetValue<Vector3>();
+                                value[elementIdx] = f;
+                                property.SetValue(value);
+                            };
+                        }
+                        break;
+                    case SerializableProperty.FieldType.Vector4:
+                        if (internalType == typeof(Vector4))
+                        {
+                            setter = f =>
+                            {
+                                Vector4 value = property.GetValue<Vector4>();
+                                value[elementIdx] = f;
+                                property.SetValue(value);
+                            };
+                        }
+                        else if (internalType == typeof(Quaternion))
+                        {
+                            setter = f =>
+                            {
+                                Quaternion value = property.GetValue<Quaternion>();
+                                value[elementIdx] = f;
+                                property.SetValue(value);
+                            };
+                        }
+                        break;
+                    case SerializableProperty.FieldType.Color:
+                        if (internalType == typeof(Color))
+                        {
+                            setter = f =>
+                            {
+                                Color value = property.GetValue<Color>();
+                                value[elementIdx] = f;
+                                property.SetValue(value);
+                            };
+                        }
+                        break;
+                    case SerializableProperty.FieldType.Bool:
+                            setter = f =>
+                            {
+                                bool value = f > 0.0f;
+                                property.SetValue(value);
+                            };
+                        break;
+                    case SerializableProperty.FieldType.Int:
+                        setter = f =>
+                        {
+                            int value = (int)f;
+                            property.SetValue(value);
+                        };
+                        break;
+                    case SerializableProperty.FieldType.Float:
+                        setter = f =>
+                        {
+                            property.SetValue(f);
+                        };
+                        break;
+                }
+
+                if (setter == null)
+                    continue;
+
+                FloatCurvePropertyInfo propertyInfo = new FloatCurvePropertyInfo();
+                propertyInfo.curveIdx = i;
+                propertyInfo.setter = setter;
+
+                newFloatProperties.Add(propertyInfo);
+            }
+
+            floatProperties = newFloatProperties.ToArray();
+        }
+
         /// <summary>
         /// <summary>
         /// Called whenever an animation event triggers.
         /// Called whenever an animation event triggers.
         /// </summary>
         /// </summary>
@@ -380,6 +699,30 @@ namespace BansheeEngine
             public AnimWrapMode wrapMode = AnimWrapMode.Loop;
             public AnimWrapMode wrapMode = AnimWrapMode.Loop;
             public float speed = 1.0f;
             public float speed = 1.0f;
         }
         }
+
+        /// <summary>
+        /// Contains information about a property animated by a generic animation curve.
+        /// </summary>
+        private class FloatCurvePropertyInfo
+        {
+            public int curveIdx;
+            public Action<float> setter;
+        }
+
+        /// <summary>
+        /// Information about a suffix used in a property path.
+        /// </summary>
+        internal struct PropertySuffixInfo
+        {
+            public PropertySuffixInfo(int elementIdx, bool isVector)
+            {
+                this.elementIdx = elementIdx;
+                this.isVector = isVector;
+            }
+
+            public int elementIdx;
+            public bool isVector;
+        }
     }
     }
 
 
     /** @} */
     /** @} */

+ 8 - 0
Source/MBansheeEngine/Animation/Interop/NativeAnimation.cs

@@ -101,6 +101,11 @@ namespace BansheeEngine
             Internal_SetState(mCachedPtr, clipPtr, ref state);
             Internal_SetState(mCachedPtr, clipPtr, ref state);
         }
         }
 
 
+        public bool GetGenericCurveValue(int curveIdx, out float value)
+        {
+            return Internal_GetGenericCurveValue(mCachedPtr, curveIdx, out value);
+        }
+
         internal NativeAnimation()
         internal NativeAnimation()
         {
         {
             Internal_Create(this);
             Internal_Create(this);
@@ -158,6 +163,9 @@ namespace BansheeEngine
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetState(IntPtr thisPtr, IntPtr clipPtr, ref AnimationClipState state);
         private static extern void Internal_SetState(IntPtr thisPtr, IntPtr clipPtr, ref AnimationClipState state);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern bool Internal_GetGenericCurveValue(IntPtr thisPtr, int curveIdx, out float value);
     }
     }
 
 
     /** @endcond */
     /** @endcond */

+ 2 - 0
Source/SBansheeEngine/Include/BsScriptAnimation.h

@@ -54,6 +54,8 @@ namespace BansheeEngine
 		static bool internal_GetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static bool internal_GetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 
 
+		static bool internal_GetGenericCurveValue(ScriptAnimation* thisPtr, UINT32 curveIdx, float* value);
+
 		typedef void(__stdcall *OnEventTriggeredThunkDef) (MonoObject*, MonoObject*, MonoString*, MonoException**);
 		typedef void(__stdcall *OnEventTriggeredThunkDef) (MonoObject*, MonoObject*, MonoString*, MonoException**);
 		static OnEventTriggeredThunkDef sOnEventTriggeredThunk;
 		static OnEventTriggeredThunkDef sOnEventTriggeredThunk;
 	};
 	};

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

@@ -47,6 +47,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_GetState", &ScriptAnimation::internal_GetState);
 		metaData.scriptClass->addInternalCall("Internal_GetState", &ScriptAnimation::internal_GetState);
 		metaData.scriptClass->addInternalCall("Internal_SetState", &ScriptAnimation::internal_SetState);
 		metaData.scriptClass->addInternalCall("Internal_SetState", &ScriptAnimation::internal_SetState);
 
 
+		metaData.scriptClass->addInternalCall("Internal_GetGenericCurveValue", &ScriptAnimation::internal_GetGenericCurveValue);
+
 		sOnEventTriggeredThunk = (OnEventTriggeredThunkDef)metaData.scriptClass->getMethod("Internal_OnEventTriggered", 2)->getThunk();
 		sOnEventTriggeredThunk = (OnEventTriggeredThunkDef)metaData.scriptClass->getMethod("Internal_OnEventTriggered", 2)->getThunk();
 	}
 	}
 
 
@@ -153,6 +155,11 @@ namespace BansheeEngine
 		thisPtr->getInternal()->setState(nativeClip, *state);
 		thisPtr->getInternal()->setState(nativeClip, *state);
 	}
 	}
 
 
+	bool ScriptAnimation::internal_GetGenericCurveValue(ScriptAnimation* thisPtr, UINT32 curveIdx, float* value)
+	{
+		return thisPtr->getInternal()->getGenericCurveValue(curveIdx, *value);
+	}
+
 	MonoField* ScriptBlendClipInfo::clipField = nullptr;
 	MonoField* ScriptBlendClipInfo::clipField = nullptr;
 	MonoField* ScriptBlendClipInfo::positionField = nullptr;
 	MonoField* ScriptBlendClipInfo::positionField = nullptr;