Browse Source

Added morph shape curve evaluation
Added morph shape curve import from FBX
Refactored morph shapes into frames in channels in order to support multiple morph shape frames per animation, rather than relying on individual weights for blending (weights can still be used on a per channel basis)

BearishSun 9 years ago
parent
commit
b89005a830

+ 23 - 8
Source/BansheeCore/Include/BsAnimation.h

@@ -126,11 +126,23 @@ namespace BansheeEngine
 		String curveName;
 	};
 
+	/** Information about a set of morph shapes blended sequentially. */
+	struct MorphChannelInfo
+	{
+		float weight;
+		UINT32 shapeStart;
+		UINT32 shapeCount;
+
+		UINT32 frameCurveIdx;
+		UINT32 weightCurveIdx;
+	};
+
 	/** Morph shape and its contribution to the final shape. */
 	struct MorphShapeInfo
 	{
 		SPtr<MorphShape> shape;
-		float weight;
+		float frameWeight;
+		float finalWeight;
 	};
 
 	/** Contains information about a scene object that is animated by a specific animation curve. */
@@ -197,7 +209,7 @@ namespace BansheeEngine
 		 * Updates the proxy data with new weights used for morph shapes. Caller must ensure the weights are ordered so
 		 * they match with the morph shapes provided to the last rebuild() call.
 		 */
-		void updateMorphShapeWeights(const Vector<float>& weights);
+		void updateMorphChannelWeights(const Vector<float>& weights);
 
 		/**
 		 * Updates the proxy data with new scene object transforms. Caller must guarantee that clip layout didn't 
@@ -230,10 +242,12 @@ namespace BansheeEngine
 		Matrix4* sceneObjectTransforms;
 
 		// Morph shape animation
+		MorphChannelInfo* morphChannelInfos;
 		MorphShapeInfo* morphShapeInfos;
+		UINT32 numMorphChannels;
 		UINT32 numMorphShapes;
 		UINT32 numMorphVertices;
-		bool morphShapeWeightsDirty;
+		bool morphChannelWeightsDirty;
 
 		// Culling
 		AABox mBounds;
@@ -270,12 +284,13 @@ namespace BansheeEngine
 		void setMorphShapes(const SPtr<MorphShapes>& morphShapes);
 
 		/**
-		 * Changes a weight of a single morph shape, determining how much of it to apply on top of the base mesh.
+		 * Changes a weight of a single morph channel, determining how much of it to apply on top of the base mesh.
 		 *
-		 * @param idx		Index of the morph shape to modify. This must match the shapes provided to setMorphShapes().
-		 * @param weight	Weight that determines how much of the shape to apply to the mesh, in range [0, 1]. 	
+		 * @param idx		Index of the morph channel to modify. This must match the channels contained in the object
+		 *					provided to setMorphShapes().
+		 * @param weight	Weight that determines how much of the channel to apply to the mesh, in range [0, 1]. 	
 		 */
-		void setMorphShapeWeight(UINT32 idx, float weight);
+		void setMorphChannelWeight(UINT32 idx, float weight);
 
 		/** 
 		 * Sets a mask that allows certain bones from the skeleton to be disabled. Caller must ensure that the mask matches
@@ -502,7 +517,7 @@ namespace BansheeEngine
 		SPtr<Skeleton> mSkeleton;
 		SkeletonMask mSkeletonMask;
 		SPtr<MorphShapes> mMorphShapes;
-		Vector<float> mMorphShapeWeights;
+		Vector<float> mMorphChannelWeights;
 		Vector<AnimationClipInfo> mClipInfos;
 		UnorderedMap<UINT64, AnimatedSceneObject> mSceneObjects;
 		Vector<float> mGenericCurveOutputs;

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

@@ -94,7 +94,10 @@ namespace BansheeEngine
 		Position,
 		Rotation,
 		Scale,
-		Generic
+		Generic,
+		MorphFrame,
+		MorphWeight,
+		Count // Keep at end
 	};
 
 	/** 
@@ -142,6 +145,16 @@ namespace BansheeEngine
 		 */
 		void getCurveMapping(const String& name, AnimationCurveMapping& mapping) const;
 
+		/** 
+		 * Attempts to find a generic curve with the specified name and fills output with found index, which can then be
+		 * used for quick lookup.
+		 *
+		 * @param[in]	name		Name of the curve to look up.
+		 * @param[out]	frameIdx	Index of the curve animating the morph shape frames, or -1 if not found.
+		 * @param[out]	weightIdx	Index of the curve animating the channel weight, or -1 if not found.
+		 */
+		void getMorphMapping(const String& name, UINT32& frameIdx, UINT32& weightIdx) const;
+
 		/** 
 		 * Checks are the curves contained within the clip additive. Additive clips are intended to be added on top of
 		 * other clips.
@@ -225,7 +238,7 @@ namespace BansheeEngine
 		/** 
 		 * Contains a map from curve name to curve index. Indices are stored as specified in CurveType enum. 
 		 */
-		UnorderedMap<String, UINT32[4]> mNameMapping;
+		UnorderedMap<String, UINT32[(int)CurveType::Count]> mNameMapping;
 
 		Vector<AnimationEvent> mEvents;
 		bool mIsAdditive;

+ 6 - 2
Source/BansheeCore/Include/BsAnimationCurve.h

@@ -131,7 +131,7 @@ namespace BansheeEngine
 		 *
 		 * @param[in]	lhs		Key to interpolate from.
 		 * @param[in]	rhs		Key to interpolate to.
-		 * @param[in]	t		Curve time to interpolate the keys at.
+		 * @param[in]	time	Curve time to interpolate the keys at.
 		 * @return				Interpolated key value.
 		 */
 		KeyFrame evaluateKey(const KeyFrame& lhs, const KeyFrame& rhs, float time) const;
@@ -158,7 +158,11 @@ namespace BansheeEngine
 	enum class AnimationCurveFlag
 	{
 		/** Signifies that the curve was imported from an external file, and not created manually in-engine. */
-		ImportedCurve
+		ImportedCurve = 1 << 0,
+		/** Signifies the curve is used to animate between different frames within a morph channel. In range [0, 1]. */
+		MorphFrame = 1 << 1,
+		/** Signifies the curve is used to adjust the weight of a morph channel. In range [0, 1]. */
+		MorphWeight = 1 << 2
 	};
 
 	typedef Flags<AnimationCurveFlag> AnimationCurveFlags;

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

@@ -377,6 +377,7 @@ namespace BansheeEngine
 	class CameraCore;
 	class MorphShapes;
 	class MorphShape;
+	class MorphChannel;
 	// Asset import
 	class SpecificImporter;
 	class Importer;
@@ -543,6 +544,7 @@ namespace BansheeEngine
 		TID_PostProcessSettings = 1127,
 		TID_MorphShape = 1128,
 		TID_MorphShapes = 1129,
+		TID_MorphChannel = 1130,
 
 		// Moved from Engine layer
 		TID_CCamera = 30000,

+ 66 - 10
Source/BansheeCore/Include/BsMorphShapes.h

@@ -32,19 +32,31 @@ namespace BansheeEngine
 	class BS_CORE_EXPORT MorphShape : public IReflectable
 	{
 	public:
-		MorphShape(const String& name, const Vector<MorphVertex>& vertices);
+		MorphShape(const String& name, float weight, const Vector<MorphVertex>& vertices);
 
 		/** Returns the name of the shape. */
 		const String& getName() const { return mName; }
 
+		/** Returns the weight of the shape, determining how are different shapes within a channel blended. */
+		float getWeight() const { return mWeight; }
+
 		/** Returns a reference to all of the shape's vertices. Contains only vertices that differ from the base. */
 		const Vector<MorphVertex>& getVertices() const { return mVertices; }
 
-		/** Creates a new morph shape from the provided set of vertices. */
-		static SPtr<MorphShape> create(const String& name, const Vector<MorphVertex>& vertices);
+		/** 
+		 * Creates a new morph shape from the provided set of vertices. 
+		 * 
+		 * @param[in]	name		Name of the frame. Must be unique within a morph channel.
+		 * @param[in]	weight		Weight in range [0, 1]. Determines how are sequential shapes animated between within a
+		 *							morph channel. e.g. if there is a shape A with weight 0.3 and shape B with weight 0.8
+		 *							then shape A will be displayed first and then be blended towards shape B as time passes.
+		 * @param[in]	vertices	Vertices of the base mesh modified by the shape.
+		 */
+		static SPtr<MorphShape> create(const String& name, float weight, const Vector<MorphVertex>& vertices);
 
 	private:
 		String mName;
+		float mWeight;
 		Vector<MorphVertex> mVertices;
 
 		/************************************************************************/
@@ -58,30 +70,74 @@ namespace BansheeEngine
 		MorphShape(); // Serialization only
 	};
 
-	/** 
-	 * Contains a set of morph shapes, used for morph target animation. Each morph shape contains a single possible shape
-	 * that can be added on top of the base shape in order to create the animation.
+	/**
+	 * A collection of morph shapes that are sequentially blended together. Each shape has a weight in range [0, 1] which
+	 * determines at what point is that shape blended. As the channel percent moves from 0 to 1, different shapes will be
+	 * blended with those before or after them, depending on their weight.
 	 */
-	class BS_CORE_EXPORT MorphShapes : public IReflectable // Note: Must be immutable in order to be usable on multiple threads
+	class BS_CORE_EXPORT MorphChannel : public IReflectable
 	{
 	public:
+		/** Returns the name of the channel. */
+		const String& getName() const { return mName; }
+
 		/** Returns the number of available morph shapes. */
 		UINT32 getNumShapes() const { return (UINT32)mShapes.size(); }
 
 		/** Returns the morph shape at the specified index. */
 		SPtr<MorphShape> getShape(UINT32 idx) const { return mShapes[idx]; }
 
+		/** Creates a new channel from a set of morph shapes. */
+		static SPtr<MorphChannel> create(const String& name, const Vector<SPtr<MorphShape>>& shapes);
+
+	private:
+		MorphChannel();
+		MorphChannel(const String& name, const Vector<SPtr<MorphShape>>& shapes);
+
+		String mName;
+		Vector<SPtr<MorphShape>> mShapes;
+
+		/************************************************************************/
+		/* 								SERIALIZATION                      		*/
+		/************************************************************************/
+	public:
+		friend class MorphChannelRTTI;
+		static RTTITypeBase* getRTTIStatic();
+		RTTITypeBase* getRTTI() const override;
+
+		/** 
+		 * Creates MorphShapes with no data. You must populate its data manually.
+		 *
+		 * @note	For serialization use only.
+		 */
+		static SPtr<MorphChannel> createEmpty();
+	};
+
+	/** 
+	 * Contains a set of morph channels used for morph target animation. Each morph channel contains one or multiple shapes
+	 * which are blended together depending on frame animation. Each channel is then additively blended together depending
+	 * on some weight.
+	 */
+	class BS_CORE_EXPORT MorphShapes : public IReflectable // Note: Must be immutable in order to be usable on multiple threads
+	{
+	public:
+		/** Returns the number of available morph channels. */
+		UINT32 getNumChannels() const { return (UINT32)mChannels.size(); }
+
+		/** Returns the morph channel at the specified index. */
+		SPtr<MorphChannel> getChannel(UINT32 idx) const { return mChannels[idx]; }
+
 		/** Returns the number of vertices per morph shape. */
 		UINT32 getNumVertices() const { return mNumVertices; }
 
 		/** Creates a new set of morph shapes. */
-		static SPtr<MorphShapes> create(const Vector<SPtr<MorphShape>>& shapes, UINT32 numVertices);
+		static SPtr<MorphShapes> create(const Vector<SPtr<MorphChannel>>& channels, UINT32 numVertices);
 
 	private:
 		MorphShapes();
-		MorphShapes(const Vector<SPtr<MorphShape>>& shapes, UINT32 numVertices);
+		MorphShapes(const Vector<SPtr<MorphChannel>>& channels, UINT32 numVertices);
 
-		Vector<SPtr<MorphShape>> mShapes;
+		Vector<SPtr<MorphChannel>> mChannels;
 		UINT32 mNumVertices;
 
 		/************************************************************************/

+ 33 - 2
Source/BansheeCore/Include/BsMorphShapesRTTI.h

@@ -18,7 +18,8 @@ namespace BansheeEngine
 	private:
 		BS_BEGIN_RTTI_MEMBERS
 			BS_RTTI_MEMBER_PLAIN(mName, 0)
-			BS_RTTI_MEMBER_PLAIN_ARRAY(mVertices, 1)
+			BS_RTTI_MEMBER_PLAIN(mWeight, 1)
+			BS_RTTI_MEMBER_PLAIN_ARRAY(mVertices, 2)
 		BS_END_RTTI_MEMBERS
 		
 	public:
@@ -43,11 +44,41 @@ namespace BansheeEngine
 		}
 	};
 
+	class BS_CORE_EXPORT MorphChannelRTTI : public RTTIType <MorphChannel, IReflectable, MorphChannelRTTI>
+	{
+	private:
+		BS_BEGIN_RTTI_MEMBERS
+			BS_RTTI_MEMBER_PLAIN(mName, 0)
+			BS_RTTI_MEMBER_REFLPTR_ARRAY(mShapes, 1)
+		BS_END_RTTI_MEMBERS
+
+	public:
+		MorphChannelRTTI()
+			:mInitMembers(this)
+		{ }
+
+		const String& getRTTIName() override
+		{
+			static String name = "MorphChannel";
+			return name;
+		}
+
+		UINT32 getRTTIId() override
+		{
+			return TID_MorphChannel;
+		}
+
+		SPtr<IReflectable> newRTTIObject() override
+		{
+			return MorphChannel::createEmpty();
+		}
+	};
+
 	class BS_CORE_EXPORT MorphShapesRTTI : public RTTIType <MorphShapes, IReflectable, MorphShapesRTTI>
 	{
 	private:
 		BS_BEGIN_RTTI_MEMBERS
-			BS_RTTI_MEMBER_REFLPTR_ARRAY(mShapes, 0)
+			BS_RTTI_MEMBER_REFLPTR_ARRAY(mChannels, 0)
 			BS_RTTI_MEMBER_PLAIN(mNumVertices, 1)
 		BS_END_RTTI_MEMBERS
 

+ 77 - 24
Source/BansheeCore/Source/BsAnimation.cpp

@@ -34,8 +34,9 @@ namespace BansheeEngine
 
 	AnimationProxy::AnimationProxy(UINT64 id)
 		: id(id), layers(nullptr), numLayers(0), numSceneObjects(0), sceneObjectInfos(nullptr)
-		, sceneObjectTransforms(nullptr), morphShapeInfos(nullptr), numMorphShapes(0), numMorphVertices(0)
-		, morphShapeWeightsDirty(false), mCullEnabled(true), numGenericCurves(0), genericCurveOutputs(nullptr)
+		, sceneObjectTransforms(nullptr), morphChannelInfos(nullptr), morphShapeInfos(nullptr), numMorphShapes(0)
+		, numMorphChannels(0), numMorphVertices(0), morphChannelWeightsDirty(false), mCullEnabled(true), numGenericCurves(0)
+		, genericCurveOutputs(nullptr)
 	{ }
 
 	AnimationProxy::~AnimationProxy()
@@ -256,11 +257,16 @@ namespace BansheeEngine
 
 			if (morphShapes != nullptr)
 			{
-				numMorphShapes = morphShapes->getNumShapes();
+				numMorphChannels = morphShapes->getNumChannels();
 				numMorphVertices = morphShapes->getNumVertices();
+
+				numMorphShapes = 0; 
+				for (UINT32 i = 0; i < numMorphChannels; i++)
+					numMorphShapes += morphShapes->getChannel(i)->getNumShapes();
 			}
 			else
 			{
+				numMorphChannels = 0;
 				numMorphShapes = 0;
 				numMorphVertices = 0;
 			}
@@ -276,11 +282,12 @@ namespace BansheeEngine
 			UINT32 genericCurveOutputSize = numGenericCurves * sizeof(float);
 			UINT32 sceneObjectIdsSize = numSceneObjects * sizeof(AnimatedSceneObjectInfo);
 			UINT32 sceneObjectTransformsSize = numBoneMappedSOs * sizeof(Matrix4);
+			UINT32 morphChannelSize = numMorphChannels * sizeof(MorphChannelInfo);
 			UINT32 morphShapeSize = numMorphShapes * sizeof(MorphShapeInfo);
 
 			UINT8* data = (UINT8*)bs_alloc(layersSize + clipsSize + boneMappingSize + posCacheSize + rotCacheSize + 
 				scaleCacheSize + genCacheSize + genericCurveOutputSize + sceneObjectIdsSize + sceneObjectTransformsSize +
-				morphShapeSize);
+				morphChannelSize + morphShapeSize);
 
 			layers = (AnimationStateLayer*)data;
 			memcpy(layers, tempLayers.data(), layersSize);
@@ -334,18 +341,64 @@ namespace BansheeEngine
 
 			data += sceneObjectTransformsSize;
 
+			morphChannelInfos = (MorphChannelInfo*)data;
+			data += morphChannelSize;
+
 			morphShapeInfos = (MorphShapeInfo*)data;
-			for (UINT32 i = 0; i < numMorphShapes; i++)
+			data += morphShapeSize;
+
+			// Generate data required for morph shape animation
+			if (morphShapes != nullptr)
 			{
-				new (&morphShapeInfos[i].shape) SPtr<MorphShape>();
+				UINT32 currentShapeIdx = 0;
+				for (UINT32 i = 0; i < numMorphChannels; i++)
+				{
+					SPtr<MorphChannel> morphChannel = morphShapes->getChannel(i);
+					UINT32 numShapes = morphChannel->getNumShapes();
 
-				morphShapeInfos[i].shape = morphShapes->getShape(i);
-				morphShapeInfos[i].weight = 0.0f;
-			}
+					MorphChannelInfo& channelInfo = morphChannelInfos[i];
+					channelInfo.weight = 0.0f;
+					channelInfo.shapeStart = currentShapeIdx;
+					channelInfo.shapeCount = numShapes;
+					channelInfo.frameCurveIdx = (UINT32)-1;
+					channelInfo.weightCurveIdx = (UINT32)-1;
 
-			morphShapeWeightsDirty = true;
-			data += morphShapeSize;
+					for (UINT32 j = 0; j < numShapes; j++)
+					{
+						MorphShapeInfo& shapeInfo = morphShapeInfos[currentShapeIdx];
+						new (&shapeInfo.shape) SPtr<MorphShape>();
+
+						SPtr<MorphShape> shape = morphChannel->getShape(currentShapeIdx);
+						shapeInfo.shape = shape;
+						shapeInfo.frameWeight = shape->getWeight();
+						shapeInfo.finalWeight = 0.0f;
+
+						currentShapeIdx++;
+					}
+				}
 
+				// Find any curves affecting morph shape animation
+				if (!clipInfos.empty())
+				{
+					bool isClipValid = clipLoadState[0];
+					if (isClipValid)
+					{
+						AnimationClipInfo& clipInfo = clipInfos[0];
+
+						for (UINT32 i = 0; i < numMorphChannels; i++)
+						{
+							SPtr<MorphChannel> morphChannel = morphShapes->getChannel(i);
+							MorphChannelInfo& channelInfo = morphChannelInfos[i];
+
+							clipInfo.clip->getMorphMapping(morphChannel->getName(), channelInfo.frameCurveIdx, 
+								channelInfo.weightCurveIdx);
+						}
+					}
+				}
+
+				morphChannelWeightsDirty = true;
+			}
+			
 			UINT32 curLayerIdx = 0;
 			UINT32 curStateIdx = 0;
 
@@ -539,18 +592,18 @@ namespace BansheeEngine
 		}
 	}
 
-	void AnimationProxy::updateMorphShapeWeights(const Vector<float>& weights)
+	void AnimationProxy::updateMorphChannelWeights(const Vector<float>& weights)
 	{
 		UINT32 numWeights = (UINT32)weights.size();
-		for(UINT32 i = 0; i < numMorphShapes; i++)
+		for(UINT32 i = 0; i < numMorphChannels; i++)
 		{
 			if (i < numWeights)
-				morphShapeInfos[i].weight = weights[i];
+				morphChannelInfos[i].weight = weights[i];
 			else
-				morphShapeInfos[i].weight = 0.0f;
+				morphChannelInfos[i].weight = 0.0f;
 		}
 
-		morphShapeWeightsDirty = true;
+		morphChannelWeightsDirty = true;
 	}
 
 	void AnimationProxy::updateTransforms(const Vector<AnimatedSceneObject>& sceneObjects)
@@ -623,23 +676,23 @@ namespace BansheeEngine
 	{
 		mMorphShapes = morphShapes;
 
-		UINT32 numShapes;
+		UINT32 numChannels;
 		if (mMorphShapes != nullptr)
-			numShapes = mMorphShapes->getNumShapes();
+			numChannels = mMorphShapes->getNumChannels();
 		else
-			numShapes = 0;
+			numChannels = 0;
 
-		mMorphShapeWeights.assign(numShapes, 0.0f);
+		mMorphChannelWeights.assign(numChannels, 0.0f);
 		mDirty |= AnimDirtyStateFlag::Layout;
 	}
 
-	void Animation::setMorphShapeWeight(UINT32 idx, float weight)
+	void Animation::setMorphChannelWeight(UINT32 idx, float weight)
 	{
-		UINT32 numShapes = (UINT32)mMorphShapeWeights.size();
+		UINT32 numShapes = (UINT32)mMorphChannelWeights.size();
 		if (idx >= numShapes)
 			return;
 
-		mMorphShapeWeights[idx] = weight;
+		mMorphChannelWeights[idx] = weight;
 		mDirty |= AnimDirtyStateFlag::MorphWeights;
 	}
 
@@ -1245,7 +1298,7 @@ namespace BansheeEngine
 			else if(mDirty.isSet(AnimDirtyStateFlag::Value))
 
 			if (mDirty.isSet(AnimDirtyStateFlag::MorphWeights))
-				mAnimProxy->updateMorphShapeWeights(mMorphShapeWeights);
+				mAnimProxy->updateMorphChannelWeights(mMorphChannelWeights);
 		}
 
 		// Check if there are dirty transforms

+ 45 - 1
Source/BansheeCore/Source/BsAnimationClip.cpp

@@ -179,7 +179,34 @@ namespace BansheeEngine
 		registerEntries(mCurves->position, CurveType::Position);
 		registerEntries(mCurves->rotation, CurveType::Rotation);
 		registerEntries(mCurves->scale, CurveType::Scale);
-		registerEntries(mCurves->generic, CurveType::Generic);
+
+		// Generic and morph curves
+		{
+			Vector<TNamedAnimationCurve<float>>& curve = mCurves->generic;
+			for (UINT32 i = 0; i < (UINT32)curve.size(); i++)
+			{
+				auto& entry = curve[i];
+
+				UINT32 typeIdx;
+				if (entry.flags.isSet(AnimationCurveFlag::MorphFrame))
+					typeIdx = (UINT32)CurveType::MorphFrame;
+				else if (entry.flags.isSet(AnimationCurveFlag::MorphWeight))
+					typeIdx = (UINT32)CurveType::MorphWeight;
+				else
+					typeIdx = (UINT32)CurveType::Generic;
+
+				auto iterFind = mNameMapping.find(entry.name);
+				if (iterFind == mNameMapping.end())
+				{
+					UINT32* indices = mNameMapping[entry.name];
+					memset(indices, -1, sizeof(UINT32) * 4);
+
+					indices[typeIdx] = i;
+				}
+				else
+					mNameMapping[entry.name][typeIdx] = i;
+			}
+		}
 	}
 
 	void AnimationClip::initialize()
@@ -215,6 +242,23 @@ namespace BansheeEngine
 			mapping = { (UINT32)-1, (UINT32)-1, (UINT32)-1 };
 	}
 
+	void AnimationClip::getMorphMapping(const String& name, UINT32& frameIdx, UINT32& weightIdx) const
+	{
+		auto iterFind = mNameMapping.find(name);
+		if (iterFind != mNameMapping.end())
+		{
+			const UINT32* indices = iterFind->second;
+
+			frameIdx = indices[(UINT32)CurveType::MorphFrame];
+			weightIdx = indices[(UINT32)CurveType::MorphWeight];
+		}
+		else
+		{
+			frameIdx = (UINT32)-1;
+			weightIdx = (UINT32)-1;
+		}
+	}
+
 	RTTITypeBase* AnimationClip::getRTTIStatic()
 	{
 		return AnimationClipRTTI::instance();

+ 116 - 5
Source/BansheeCore/Source/BsAnimationManager.cpp

@@ -278,7 +278,118 @@ namespace BansheeEngine
 				else
 					animInfo.morphShapeInfo.version = 1; // 0 is considered invalid version
 
-				if(anim->morphShapeWeightsDirty)
+				// Recalculate weights if curves are present
+				bool hasMorphCurves = false;
+				for(UINT32 i = 0; i < anim->numMorphChannels; i++)
+				{
+					MorphChannelInfo& channelInfo = anim->morphChannelInfos[i];
+					if(channelInfo.weightCurveIdx != (UINT32)-1)
+					{
+						channelInfo.weight = Math::clamp01(anim->genericCurveOutputs[channelInfo.weightCurveIdx]);
+						hasMorphCurves = true;
+					}
+
+					float frameWeight;
+					if (channelInfo.frameCurveIdx != (UINT32)-1)
+					{
+						frameWeight = Math::clamp01(anim->genericCurveOutputs[channelInfo.frameCurveIdx]);
+						hasMorphCurves = true;
+					}
+					else
+						frameWeight = 0.0f;
+
+					if(channelInfo.shapeCount == 1)
+					{
+						MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart];
+						shapeInfo.finalWeight = 1.0f;
+					}
+					else if(channelInfo.shapeCount > 1)
+					{
+						// First frame
+						{
+							MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart];
+							MorphShapeInfo& nextShapeInfo = anim->morphShapeInfos[channelInfo.shapeStart + 1];
+
+							float relative = frameWeight - shapeInfo.frameWeight;
+							if (relative <= 0.0f)
+								shapeInfo.finalWeight = 1.0f;
+							else
+							{
+								float diff = nextShapeInfo.frameWeight - shapeInfo.frameWeight;
+								if(diff > 0.0f)
+								{
+									float t = relative / diff;
+									shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
+								}
+								else
+									shapeInfo.finalWeight = 0.0f;
+							}
+						}
+
+						// Middle frames
+						for(UINT32 j = 1; j < channelInfo.shapeCount - 1; j++)
+						{
+							MorphShapeInfo& prevShapeInfo = anim->morphShapeInfos[j - 1];
+							MorphShapeInfo& shapeInfo = anim->morphShapeInfos[j];
+							MorphShapeInfo& nextShapeInfo = anim->morphShapeInfos[j + 1];
+
+							float relative = frameWeight - shapeInfo.frameWeight;
+							if (relative <= 0.0f)
+							{
+								float diff = shapeInfo.frameWeight - prevShapeInfo.frameWeight;
+								if (diff > 0.0f)
+								{
+									float t = relative / diff;
+									shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
+								}
+								else
+									shapeInfo.finalWeight = 0.0f;
+							}
+							else
+							{
+								float diff = nextShapeInfo.frameWeight - shapeInfo.frameWeight;
+								if (diff > 0.0f)
+								{
+									float t = relative / diff;
+									shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
+								}
+								else
+									shapeInfo.finalWeight = 0.0f;
+							}
+						}
+
+						// Last frame
+						{
+							UINT32 lastFrame = channelInfo.shapeStart + channelInfo.shapeCount - 1;
+							MorphShapeInfo& prevShapeInfo = anim->morphShapeInfos[lastFrame - 1];
+							MorphShapeInfo& shapeInfo = anim->morphShapeInfos[lastFrame];
+
+							float relative = frameWeight - shapeInfo.frameWeight;
+							if (relative <= 0.0f)
+							{
+								float diff = shapeInfo.frameWeight - prevShapeInfo.frameWeight;
+								if (diff > 0.0f)
+								{
+									float t = relative / diff;
+									shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
+								}
+								else
+									shapeInfo.finalWeight = 0.0f;
+							}
+							else
+								shapeInfo.finalWeight = 1.0f;
+						}
+					}
+
+					for(UINT32 j = 0; j < channelInfo.shapeCount; j++)
+					{
+						MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart + j];
+						shapeInfo.finalWeight *= channelInfo.weight;
+					}
+				}
+
+				// Generate morph shape vertices
+				if(anim->morphChannelWeightsDirty || hasMorphCurves)
 				{
 					SPtr<MeshData> meshData = bs_shared_ptr_new<MeshData>(anim->numMorphVertices, 0, mBlendShapeVertexDesc);
 
@@ -300,7 +411,7 @@ namespace BansheeEngine
 					for(UINT32 i = 0; i < anim->numMorphShapes; i++)
 					{
 						const MorphShapeInfo& info = anim->morphShapeInfos[i];
-						float absWeight = Math::abs(info.weight);
+						float absWeight = Math::abs(info.finalWeight);
 
 						if (absWeight < 0.0001f)
 							continue;
@@ -312,9 +423,9 @@ namespace BansheeEngine
 							const MorphVertex& vertex = morphVertices[j];
 
 							Vector3* destPos = (Vector3*)(positions + vertex.sourceIdx * stride);
-							*destPos += vertex.deltaPosition * info.weight;
+							*destPos += vertex.deltaPosition * info.finalWeight;
 
-							tempNormals[vertex.sourceIdx] += vertex.deltaNormal * info.weight;
+							tempNormals[vertex.sourceIdx] += vertex.deltaNormal * info.finalWeight;
 							accumulatedWeight[vertex.sourceIdx] += absWeight;
 						}
 					}
@@ -342,7 +453,7 @@ namespace BansheeEngine
 					animInfo.morphShapeInfo.meshData = meshData;
 
 					animInfo.morphShapeInfo.version++;
-					anim->morphShapeWeightsDirty = false;
+					anim->morphChannelWeightsDirty = false;
 				}
 
 				hasAnimInfo = true;

+ 43 - 8
Source/BansheeCore/Source/BsMorphShapes.cpp

@@ -8,14 +8,14 @@ namespace BansheeEngine
 	MorphShape::MorphShape()
 	{ }
 
-	MorphShape::MorphShape(const String& name, const Vector<MorphVertex>& vertices)
-		:mName(name), mVertices(vertices)
+	MorphShape::MorphShape(const String& name, float weight, const Vector<MorphVertex>& vertices)
+		:mName(name), mWeight(weight), mVertices(vertices)
 	{ }
 
 	/** Creates a new morph shape from the provided set of vertices. */
-	SPtr<MorphShape> MorphShape::create(const String& name, const Vector<MorphVertex>& vertices)
+	SPtr<MorphShape> MorphShape::create(const String& name, float weight, const Vector<MorphVertex>& vertices)
 	{
-		return bs_shared_ptr_new<MorphShape>(name, vertices);
+		return bs_shared_ptr_new<MorphShape>(name, weight, vertices);
 	}
 
 	RTTITypeBase* MorphShape::getRTTIStatic()
@@ -28,18 +28,53 @@ namespace BansheeEngine
 		return getRTTIStatic();
 	}
 
+	MorphChannel::MorphChannel()
+	{ }
+
+	MorphChannel::MorphChannel(const String& name, const Vector<SPtr<MorphShape>>& shapes)
+		:mName(name), mShapes(shapes)
+	{
+		std::sort(mShapes.begin(), mShapes.end(), 
+			[](auto& x, auto& y)
+		{
+			return x->getWeight() < y->getWeight();
+		});
+	}
+
+	SPtr<MorphChannel> MorphChannel::create(const String& name, const Vector<SPtr<MorphShape>>& shapes)
+	{
+		MorphChannel* raw = new (bs_alloc<MorphChannel>()) MorphChannel(name, shapes);
+		return bs_shared_ptr(raw);
+	}
+
+	SPtr<MorphChannel> MorphChannel::createEmpty()
+	{
+		MorphChannel* raw = new (bs_alloc<MorphChannel>()) MorphChannel();
+		return bs_shared_ptr(raw);
+	}
+
+	RTTITypeBase* MorphChannel::getRTTIStatic()
+	{
+		return MorphChannelRTTI::instance();
+	}
+
+	RTTITypeBase* MorphChannel::getRTTI() const
+	{
+		return getRTTIStatic();
+	}
+
 	MorphShapes::MorphShapes()
 	{ }
 
-	MorphShapes::MorphShapes(const Vector<SPtr<MorphShape>>& shapes, UINT32 numVertices)
-		:mShapes(shapes), mNumVertices(numVertices)
+	MorphShapes::MorphShapes(const Vector<SPtr<MorphChannel>>& channels, UINT32 numVertices)
+		:mChannels(channels), mNumVertices(numVertices)
 	{
 
 	}
 
-	SPtr<MorphShapes> MorphShapes::create(const Vector<SPtr<MorphShape>>& shapes, UINT32 numVertices)
+	SPtr<MorphShapes> MorphShapes::create(const Vector<SPtr<MorphChannel>>& channels, UINT32 numVertices)
 	{
-		MorphShapes* raw = new (bs_alloc<MorphShapes>()) MorphShapes(shapes, numVertices);
+		MorphShapes* raw = new (bs_alloc<MorphShapes>()) MorphShapes(channels, numVertices);
 		return bs_shared_ptr(raw);
 	}
 

+ 2 - 2
Source/BansheeEngine/Include/BsCAnimation.h

@@ -77,8 +77,8 @@ namespace BansheeEngine
 		/** @copydoc Animation::setState */
 		void setState(const HAnimationClip& clip, AnimationClipState state);
 
-		/** @copydoc Animation::setMorphShapeWeight */
-		void setMorphShapeWeight(UINT32 idx, float weight);
+		/** @copydoc Animation::setMorphChannelWeight */
+		void setMorphChannelWeight(UINT32 idx, float weight);
 
 		/** Sets bounds that will be used for animation and mesh culling. Only relevant if setUseBounds() is set to true. */
 		void setBounds(const AABox& bounds);

+ 2 - 2
Source/BansheeEngine/Source/BsCAnimation.cpp

@@ -118,10 +118,10 @@ namespace BansheeEngine
 			return mInternal->setState(clip, state);
 	}
 
-	void CAnimation::setMorphShapeWeight(UINT32 idx, float weight)
+	void CAnimation::setMorphChannelWeight(UINT32 idx, float weight)
 	{
 		if (mInternal != nullptr)
-			return mInternal->setMorphShapeWeight(idx, weight);
+			return mInternal->setMorphChannelWeight(idx, weight);
 	}
 
 	void CAnimation::setBounds(const AABox& bounds)

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

@@ -342,10 +342,11 @@ namespace BansheeEngine
 		struct RawMorphShape
 		{
 			String name;
+			float weight;
 			Vector<MorphVertex> vertices;
 		};
 
-		UnorderedMap<String, RawMorphShape> allRawMorphShapes;
+		UnorderedMap<String, UnorderedMap<String, RawMorphShape>> allRawMorphShapes;
 		UINT32 totalNumVertices = 0;
 
 		// Note: Order in which we combine meshes must match the order in MeshData::combine
@@ -364,10 +365,13 @@ namespace BansheeEngine
 				// Copy & transform positions
 				for(auto& blendShape : mesh->blendShapes)
 				{
+					UnorderedMap<String, RawMorphShape>& channelShapes = allRawMorphShapes[blendShape.name];
+
 					for(auto& blendFrame : blendShape.frames)
 					{
-						RawMorphShape& shape = allRawMorphShapes[blendFrame.name];
+						RawMorphShape& shape = channelShapes[blendFrame.name];
 						shape.name = blendFrame.name;
+						shape.weight = blendFrame.weight;
 
 						UINT32 frameNumVertices = (UINT32)blendFrame.positions.size();
 						if (frameNumVertices == numVertices)
@@ -403,17 +407,28 @@ namespace BansheeEngine
 
 		// Create morph shape object from combined shape data
 		SPtr<MorphShapes> morphShapes;
-		Vector<SPtr<MorphShape>> allMorphShapes;
-		for (auto& entry : allRawMorphShapes)
+		Vector<SPtr<MorphChannel>> allChannels;
+		for (auto& channel : allRawMorphShapes)
 		{
-			entry.second.vertices.shrink_to_fit();
+			Vector<SPtr<MorphShape>> channelShapes;
+			for (auto& entry : channel.second)
+			{
+				RawMorphShape& shape = entry.second;
+				shape.vertices.shrink_to_fit();
 
-			SPtr<MorphShape> shape = MorphShape::create(entry.second.name, entry.second.vertices);
-			allMorphShapes.push_back(shape);
+				SPtr<MorphShape> morphShape = MorphShape::create(shape.name, shape.weight, shape.vertices);
+				channelShapes.push_back(morphShape);
+			}
+
+			if(channelShapes.size() > 0)
+			{
+				SPtr<MorphChannel> morphChannel = MorphChannel::create(channel.first, channelShapes);
+				allChannels.push_back(morphChannel);
+			}
 		}
 
-		if (!allMorphShapes.empty())
-			return MorphShapes::create(allMorphShapes, totalNumVertices);
+		if (!allChannels.empty())
+			return MorphShapes::create(allChannels, totalNumVertices);
 
 		return morphShapes;
 	}
@@ -621,7 +636,11 @@ namespace BansheeEngine
 		for (auto& clip : clips)
 		{
 			SPtr<AnimationCurves> curves = bs_shared_ptr_new<AnimationCurves>();
-			
+
+			/************************************************************************/
+			/* 							BONE ANIMATIONS                      		*/
+			/************************************************************************/
+
 			// Offset animations so they start at time 0
 			float animStart = std::numeric_limits<float>::infinity();
 
@@ -637,6 +656,13 @@ namespace BansheeEngine
 					animStart = std::min(bone.scale.getKeyFrame(0).time, animStart);
 			}
 
+			for (auto& anim : clip.blendShapeAnimations)
+			{
+				if (anim.curve.getNumKeyFrames() > 0)
+					animStart = std::min(anim.curve.getKeyFrame(0).time, animStart);
+			}
+
+			AnimationCurveFlags blendShapeFlags = AnimationCurveFlag::ImportedCurve | AnimationCurveFlag::MorphFrame;
 			if (animStart != 0.0f && animStart != std::numeric_limits<float>::infinity())
 			{
 				for (auto& bone : clip.boneAnimations)
@@ -649,6 +675,12 @@ namespace BansheeEngine
 					curves->rotation.push_back({ bone.node->name, AnimationCurveFlag::ImportedCurve, rotation });
 					curves->scale.push_back({ bone.node->name, AnimationCurveFlag::ImportedCurve, scale });
 				}
+
+				for (auto& anim : clip.blendShapeAnimations)
+				{
+					TAnimationCurve<float> curve = AnimationUtility::offsetCurve(anim.curve, -animStart);
+					curves->generic.push_back({ anim.blendShape, blendShapeFlags, curve });
+				}
 			}
 			else
 			{
@@ -658,6 +690,9 @@ namespace BansheeEngine
 					curves->rotation.push_back({ bone.node->name, AnimationCurveFlag::ImportedCurve, bone.rotation });
 					curves->scale.push_back({ bone.node->name, AnimationCurveFlag::ImportedCurve, bone.scale });
 				}
+
+				for (auto& anim : clip.blendShapeAnimations)
+					curves->generic.push_back({ anim.blendShape, blendShapeFlags, anim.curve });
 			}
 
 			// See if any splits are required. We only split the first clip as it is assumed if FBX has multiple clips the
@@ -1681,6 +1716,9 @@ namespace BansheeEngine
 
 							FbxAnimCurve* curves[1] = { curve };
 							blendShapeAnim.curve = importCurve<float, 1>(curves, importOptions, clip.start, clip.end);
+
+							// FBX contains data in [0, 100] range, but we need it in [0, 1] range
+							blendShapeAnim.curve = AnimationUtility::scaleCurve(blendShapeAnim.curve, 0.01f);
 						}
 					}
 				}

+ 15 - 8
Source/MBansheeEngine/Animation/Animation.cs

@@ -377,12 +377,13 @@ namespace BansheeEngine
         }
 
         /// <summary>
-        /// Changes a weight of a single morph shape, determining how much of it to apply on top of the base mesh.
+        /// Changes a weight of a single morph channel, determining how much of it to apply on top of the base mesh.
         /// </summary>
-        /// <param name="name">Name of the morph shape to modify the weight for. This depends on the mesh the animation
+        /// <param name="name">Name of the morph channel to modify the weight for. This depends on the mesh the animation
         ///                    is currently animating.</param>
-        /// <param name="weight">Weight that determines how much of the shape to apply to the mesh, in range[0, 1].</param>
-        public void SetMorphShapeWeight(string name, float weight)
+        /// <param name="weight">Weight that determines how much of the channel to apply to the mesh, in range[0, 1].
+        ///                     </param>
+        public void SetMorphChannelWeight(string name, float weight)
         {
             switch (state)
             {
@@ -398,12 +399,12 @@ namespace BansheeEngine
                     if (morphShapes == null)
                         return;
 
-                    string[] shapeNames = morphShapes.Shapes;
-                    for (int i = 0; i < shapeNames.Length; i++)
+                    MorphChannel[] channels = morphShapes.Channels;
+                    for (int i = 0; i < channels.Length; i++)
                     {
-                        if (shapeNames[i] == name)
+                        if (channels[i].Name == name)
                         {
-                            _native.SetMorphShapeWeight(i, weight);
+                            _native.SetMorphChannelWeight(i, weight);
                             break;
                         }
                     }
@@ -1114,6 +1115,12 @@ namespace BansheeEngine
             List<FloatCurvePropertyInfo> newFloatProperties = new List<FloatCurvePropertyInfo>();
             for (int i = 0; i < curves.FloatCurves.Length; i++)
             {
+                bool isMorphCurve = curves.FloatCurves[i].Flags.HasFlag(AnimationCurveFlags.MorphWeight) ||
+                                    curves.FloatCurves[i].Flags.HasFlag(AnimationCurveFlags.MorphFrame);
+
+                if (isMorphCurve)
+                    continue;
+
                 string suffix;
                 SerializableProperty property = FindProperty(SceneObject, curves.FloatCurves[i].Name, out suffix);
                 if (property == null)

+ 9 - 1
Source/MBansheeEngine/Animation/AnimationCurve.cs

@@ -40,7 +40,15 @@ namespace BansheeEngine
         /// how are animation results applied to scene objects (with imported animations it is assumed the curve is
         /// animating bones and with in-engine curves it is assumed the curve is animating scene objects).
         /// </summary>
-        ImportedCurve
+        ImportedCurve = 1 << 0,
+        /// <summary>
+        /// Signifies the curve is used to animate between different frames within a morph channel. In range [0, 1].
+        /// </summary>
+        MorphFrame = 1 << 1,
+        /// <summary>
+        /// Signifies the curve is used to adjust the weight of a morph channel. In range [0, 1].
+        /// </summary>
+        MorphWeight = 1 << 2
     }
 
     /// <summary>

+ 3 - 3
Source/MBansheeEngine/Animation/Interop/NativeAnimation.cs

@@ -130,9 +130,9 @@ namespace BansheeEngine
             Internal_SetState(mCachedPtr, clipPtr, ref state);
         }
 
-        public void SetMorphShapeWeight(int idx, float weight)
+        public void SetMorphChannelWeight(int idx, float weight)
         {
-            Internal_SetMorphShapeWeight(mCachedPtr, idx, weight);
+            Internal_SetMorphChannelWeight(mCachedPtr, idx, weight);
         }
 
         public bool GetGenericCurveValue(int curveIdx, out float value)
@@ -218,7 +218,7 @@ namespace BansheeEngine
         private static extern void Internal_SetState(IntPtr thisPtr, IntPtr clipPtr, ref AnimationClipState state);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_SetMorphShapeWeight(IntPtr thisPtr, int idx, float weight);
+        private static extern void Internal_SetMorphChannelWeight(IntPtr thisPtr, int idx, float weight);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern int Internal_GetNumClips(IntPtr thisPtr);

+ 64 - 4
Source/MBansheeEngine/Animation/MorphShapes.cs

@@ -9,6 +9,66 @@ namespace BansheeEngine
      *  @{
      */
 
+    /// <summary>
+    /// Name and weight of a single shape in a morph target animation. Each shape internally represents a set of vertices
+    /// that describe the morph shape.
+    /// </summary>
+    public class MorphShape
+    {
+        /// <summary>
+        /// Unique name of the shape within a channel.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Weight of the shape, determining how are different shapes within a channel blended.
+        /// </summary>
+        public float Weight { get; }
+
+        /// <summary>
+        /// Constructor for internal runtime use.
+        /// </summary>
+        /// <param name="name">Unique name of the shape within a channel.</param>
+        /// <param name="weight">Weight in range [0, 1]. Determines how are sequential shapes animated between within a
+        ///                      morph channel.e.g. if there is a shape A with weight 0.3 and shape B with weight 0.8
+        ///                      then shape A will be displayed first and then be blended towards shape B as time passes.
+        ///                      </param>
+        private MorphShape(string name, float weight)
+        {
+            Name = name;
+            Weight = weight;
+        }
+    }
+
+    /// <summary>
+    /// A collection of morph shapes that are sequentially blended together. Each shape has a weight in range [0, 1] which
+    /// determines at what point is that shape blended. As the channel percent moves from 0 to 1, different shapes will be
+    /// blended with those before or after them, depending on their weight.
+    /// </summary>
+    public class MorphChannel
+    {
+        /// <summary>
+        /// Unique name of the channel, within a single morph animation.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// All morph shapes within a channel, in order from lowest to highest weight.
+        /// </summary>
+        public MorphShape[] Shapes { get; }
+
+        /// <summary>
+        /// Constructor for internal runtime use.
+        /// </summary>
+        /// <param name="name">Unique name of the channel, within a single morph animation.</param>
+        /// <param name="shapes">A set of shapes beloning to a channel.</param>
+        private MorphChannel(string name, MorphShape[] shapes)
+        {
+            Name = name;
+            Shapes = shapes;
+        }
+    }
+
     /// <summary>
     /// Contains a set of morph shapes, used for morph target animation. Each morph shape contains a single possible shape
     /// that can be added on top of the base shape in order to create the animation.
@@ -22,15 +82,15 @@ namespace BansheeEngine
         { }
 
         /// <summary>
-        /// Returns a list of names of all available morph shapes.
+        /// Returns a list of all available morph channels.
         /// </summary>
-        public string[] Shapes
+        public MorphChannel[] Channels
         {
-            get { return Internal_GetShapes(mCachedPtr); }
+            get { return Internal_GetChannels(mCachedPtr); }
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern string[] Internal_GetShapes(IntPtr instance);
+        private static extern MorphChannel[] Internal_GetChannels(IntPtr instance);
     }
 
     /** @} */

+ 1 - 1
Source/SBansheeEngine/Include/BsScriptAnimation.h

@@ -55,7 +55,7 @@ namespace BansheeEngine
 		static bool internal_GetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 
-		static void internal_SetMorphShapeWeight(ScriptAnimation* thisPtr, UINT32 idx, float weight);
+		static void internal_SetMorphChannelWeight(ScriptAnimation* thisPtr, UINT32 idx, float weight);
 
 		static UINT32 internal_GetNumClips(ScriptAnimation* thisPtr);
 		static MonoObject* internal_GetClip(ScriptAnimation* thisPtr, UINT32 idx);

+ 27 - 1
Source/SBansheeEngine/Include/BsScriptMorphShapes.h

@@ -34,7 +34,33 @@ namespace BansheeEngine
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/
 		/************************************************************************/
-		static MonoArray* internal_GetShapes(ScriptMorphShapes* thisPtr);
+		static MonoArray* internal_GetChannels(ScriptMorphShapes* thisPtr);
+	};
+
+	/**	Interop class between C++ & CLR for MorphChannel. */
+	class BS_SCR_BE_EXPORT ScriptMorphChannel : public ScriptObject<ScriptMorphChannel>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "MorphChannel")
+
+		/** Converts native object to its managed counterpart. */
+		static MonoObject* toManaged(const SPtr<MorphChannel>& value);
+
+	private:
+		ScriptMorphChannel(MonoObject* instance);
+	};
+
+	/**	Interop class between C++ & CLR for MorphShape. */
+	class BS_SCR_BE_EXPORT ScriptMorphShape : public ScriptObject<ScriptMorphShape>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "MorphShape")
+
+		/** Converts native object to its managed counterpart. */
+		static MonoObject* toManaged(const SPtr<MorphShape>& value);
+
+	private:
+		ScriptMorphShape(MonoObject* instance);
 	};
 
 	/** @} */

+ 3 - 3
Source/SBansheeEngine/Source/BsScriptAnimation.cpp

@@ -49,7 +49,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_GetState", &ScriptAnimation::internal_GetState);
 		metaData.scriptClass->addInternalCall("Internal_SetState", &ScriptAnimation::internal_SetState);
 
-		metaData.scriptClass->addInternalCall("Internal_SetMorphShapeWeight", &ScriptAnimation::internal_SetMorphShapeWeight);
+		metaData.scriptClass->addInternalCall("Internal_SetMorphChannelWeight", &ScriptAnimation::internal_SetMorphChannelWeight);
 
 		metaData.scriptClass->addInternalCall("Internal_GetNumClips", &ScriptAnimation::internal_GetNumClips);
 		metaData.scriptClass->addInternalCall("Internal_GetClip", &ScriptAnimation::internal_GetClip);
@@ -194,9 +194,9 @@ namespace BansheeEngine
 		thisPtr->getInternal()->setState(nativeClip, *state);
 	}
 
-	void ScriptAnimation::internal_SetMorphShapeWeight(ScriptAnimation* thisPtr, UINT32 idx, float weight)
+	void ScriptAnimation::internal_SetMorphChannelWeight(ScriptAnimation* thisPtr, UINT32 idx, float weight)
 	{
-		thisPtr->getInternal()->setMorphShapeWeight(idx, weight);
+		thisPtr->getInternal()->setMorphChannelWeight(idx, weight);
 	}
 
 	bool ScriptAnimation::internal_GetGenericCurveValue(ScriptAnimation* thisPtr, UINT32 curveIdx, float* value)

+ 46 - 7
Source/SBansheeEngine/Source/BsScriptMorphShapes.cpp

@@ -19,7 +19,7 @@ namespace BansheeEngine
 
 	void ScriptMorphShapes::initRuntimeData()
 	{
-		metaData.scriptClass->addInternalCall("Internal_GetShapes", &ScriptMorphShapes::internal_GetShapes);
+		metaData.scriptClass->addInternalCall("Internal_GetChannels", &ScriptMorphShapes::internal_GetChannels);
 	}
 
 	MonoObject* ScriptMorphShapes::create(const SPtr<MorphShapes>& morphShapes)
@@ -30,19 +30,58 @@ namespace BansheeEngine
 		return instance;
 	}
 
-	MonoArray* ScriptMorphShapes::internal_GetShapes(ScriptMorphShapes* thisPtr)
+	MonoArray* ScriptMorphShapes::internal_GetChannels(ScriptMorphShapes* thisPtr)
 	{
 		SPtr<MorphShapes> morphShapes = thisPtr->getInternal();
 
-		UINT32 numShapes = morphShapes->getNumShapes();
-		ScriptArray scriptArray(MonoUtil::getStringClass(), numShapes);
+		UINT32 numChannels = morphShapes->getNumChannels();
+		ScriptArray scriptArray = ScriptArray::create<ScriptMorphChannel>(numChannels);
 
-		for (UINT32 i = 0; i < numShapes; i++)
+		for (UINT32 i = 0; i < numChannels; i++)
 		{
-			MonoString* monoString = MonoUtil::stringToMono(morphShapes->getShape(i)->getName());
-			scriptArray.set(i, monoString);
+			MonoObject* monoChannel = ScriptMorphChannel::toManaged(morphShapes->getChannel(i));
+			scriptArray.set(i, monoChannel);
 		}
 
 		return scriptArray.getInternal();
 	}
+
+	ScriptMorphChannel::ScriptMorphChannel(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptMorphChannel::initRuntimeData()
+	{ }
+
+	MonoObject* ScriptMorphChannel::toManaged(const SPtr<MorphChannel>& value)
+	{
+		MonoString* monoName = MonoUtil::stringToMono(value->getName());
+
+		UINT32 numShapes = value->getNumShapes();
+		ScriptArray shapeArray = ScriptArray::create<ScriptMorphShape>(numShapes);
+		for(UINT32 i = 0; i < numShapes; i++)
+		{
+			MonoObject* managedShape = ScriptMorphShape::toManaged(value->getShape(i));
+			shapeArray.set(i, managedShape);
+		}
+
+		void* params[2] = { monoName, shapeArray.getInternal() };
+		return metaData.scriptClass->createInstance("string,MorphShape[]", params);
+	}
+
+	ScriptMorphShape::ScriptMorphShape(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptMorphShape::initRuntimeData()
+	{ }
+
+	MonoObject* ScriptMorphShape::toManaged(const SPtr<MorphShape>& value)
+	{
+		MonoString* monoName = MonoUtil::stringToMono(value->getName());
+		float weight = value->getWeight();
+
+		void* params[2] = { monoName, &weight };
+		return metaData.scriptClass->createInstance("string,single", params);
+	}
 }