Browse Source

Bugfix: Fixing animation import from FBX
- Skeleton hierarchies containing non-bone nodes will now be animated properly
- Meshes containing non-identity geometry transform will now be animated properly

BearishSun 8 years ago
parent
commit
04db697dce

+ 8 - 4
Source/BansheeCore/Animation/BsSkeleton.cpp

@@ -85,14 +85,15 @@ namespace bs
 	}
 	}
 
 
 	Skeleton::Skeleton()
 	Skeleton::Skeleton()
-		: mNumBones(0), mInvBindPoses(nullptr), mBoneInfo(nullptr)
 	{ }
 	{ }
 
 
 	Skeleton::Skeleton(BONE_DESC* bones, UINT32 numBones)
 	Skeleton::Skeleton(BONE_DESC* bones, UINT32 numBones)
-		: mNumBones(numBones), mInvBindPoses(bs_newN<Matrix4>(numBones)), mBoneInfo(bs_newN<SkeletonBoneInfo>(numBones))
+		: mNumBones(numBones), mBoneTransforms(bs_newN<Matrix4>(numBones)), mInvBindPoses(bs_newN<Matrix4>(numBones))
+		, mBoneInfo(bs_newN<SkeletonBoneInfo>(numBones))
 	{
 	{
 		for(UINT32 i = 0; i < numBones; i++)
 		for(UINT32 i = 0; i < numBones; i++)
 		{
 		{
+			mBoneTransforms[i] = bones[i].localTfrm;
 			mInvBindPoses[i] = bones[i].invBindPose;
 			mInvBindPoses[i] = bones[i].invBindPose;
 			mBoneInfo[i].name = bones[i].name;
 			mBoneInfo[i].name = bones[i].name;
 			mBoneInfo[i].parent = bones[i].parent;
 			mBoneInfo[i].parent = bones[i].parent;
@@ -101,6 +102,9 @@ namespace bs
 
 
 	Skeleton::~Skeleton()
 	Skeleton::~Skeleton()
 	{
 	{
+		if(mBoneTransforms != nullptr)
+			bs_deleteN(mBoneTransforms, mNumBones);
+
 		if(mInvBindPoses != nullptr)
 		if(mInvBindPoses != nullptr)
 			bs_deleteN(mInvBindPoses, mNumBones);
 			bs_deleteN(mInvBindPoses, mNumBones);
 
 
@@ -287,13 +291,13 @@ namespace bs
 			pose[i] = Matrix4::TRS(localPose.positions[i], localPose.rotations[i], localPose.scales[i]);
 			pose[i] = Matrix4::TRS(localPose.positions[i], localPose.rotations[i], localPose.scales[i]);
 		}
 		}
 
 
-		// Apply bind pose transforms to non-animated bones (so that any potential child bones are transformed properly)
+		// Apply default local tranform to non-animated bones (so that any potential child bones are transformed properly)
 		for(UINT32 i = 0; i < mNumBones; i++)
 		for(UINT32 i = 0; i < mNumBones; i++)
 		{
 		{
 			if(hasAnimCurve[i])
 			if(hasAnimCurve[i])
 				continue;
 				continue;
 
 
-			pose[i] = pose[i] * mInvBindPoses[i].inverseAffine();
+			pose[i] = mBoneTransforms[i] * pose[i];
 		}
 		}
 
 
 		// Calculate global poses
 		// Calculate global poses

+ 5 - 3
Source/BansheeCore/Animation/BsSkeleton.h

@@ -34,6 +34,7 @@ namespace bs
 		String name; /**< Unique name of the bone. */
 		String name; /**< Unique name of the bone. */
 		UINT32 parent; /**< Index of the parent bone, if any. -1 if root bone. */
 		UINT32 parent; /**< Index of the parent bone, if any. -1 if root bone. */
 
 
+		Matrix4 localTfrm; /**< Local transform of the bone, relative to other bones in the hierarchy. */
 		Matrix4 invBindPose; /**< Inverse bind pose which transforms vertices from their bind pose into local space. */
 		Matrix4 invBindPose; /**< Inverse bind pose which transforms vertices from their bind pose into local space. */
 	};
 	};
 
 
@@ -175,9 +176,10 @@ namespace bs
 		Skeleton();
 		Skeleton();
 		Skeleton(BONE_DESC* bones, UINT32 numBones);
 		Skeleton(BONE_DESC* bones, UINT32 numBones);
 
 
-		UINT32 mNumBones;
-		Matrix4* mInvBindPoses;
-		SkeletonBoneInfo* mBoneInfo;
+		UINT32 mNumBones = 0;
+		Matrix4* mBoneTransforms = nullptr;
+		Matrix4* mInvBindPoses = nullptr;
+		SkeletonBoneInfo* mBoneInfo = nullptr;
 
 
 		/************************************************************************/
 		/************************************************************************/
 		/* 								SERIALIZATION                      		*/
 		/* 								SERIALIZATION                      		*/

+ 13 - 0
Source/BansheeCore/Private/RTTI/BsSkeletonRTTI.h

@@ -38,6 +38,17 @@ namespace bs
 			obj->mBoneInfo = bs_newN<SkeletonBoneInfo>(size);
 			obj->mBoneInfo = bs_newN<SkeletonBoneInfo>(size);
 		}
 		}
 
 
+		Matrix4& getBoneTransform(Skeleton* obj, UINT32 idx) { return obj->mBoneTransforms[idx]; }
+		void setBoneTransform(Skeleton* obj, UINT32 idx, Matrix4& value) { obj->mBoneTransforms[idx] = value; }
+
+		void setNumBoneTransforms(Skeleton* obj, UINT32 size)
+		{
+			obj->mNumBones = size;
+			
+			assert(obj->mBoneTransforms == nullptr);
+			obj->mBoneTransforms = bs_newN<Matrix4>(size);
+		}
+
 		UINT32 getNumBones(Skeleton* obj) { return obj->mNumBones; }
 		UINT32 getNumBones(Skeleton* obj) { return obj->mNumBones; }
 	public:
 	public:
 		SkeletonRTTI()
 		SkeletonRTTI()
@@ -46,6 +57,8 @@ namespace bs
 				&SkeletonRTTI::setBindPose, &SkeletonRTTI::setNumBindPoses);
 				&SkeletonRTTI::setBindPose, &SkeletonRTTI::setNumBindPoses);
 			addPlainArrayField("boneInfo", 1, &SkeletonRTTI::getBoneInfo, &SkeletonRTTI::getNumBones,
 			addPlainArrayField("boneInfo", 1, &SkeletonRTTI::getBoneInfo, &SkeletonRTTI::getNumBones,
 				&SkeletonRTTI::setBoneInfo, &SkeletonRTTI::setNumBoneInfos);
 				&SkeletonRTTI::setBoneInfo, &SkeletonRTTI::setNumBoneInfos);
+			addPlainArrayField("boneTransforms", 2, &SkeletonRTTI::getBoneTransform, &SkeletonRTTI::getNumBones,
+				&SkeletonRTTI::setBoneTransform, &SkeletonRTTI::setNumBoneTransforms);
 		}
 		}
 
 
 		const String& getRTTIName() override
 		const String& getRTTIName() override

+ 2 - 0
Source/BansheeFBXImporter/BsFBXImportData.h

@@ -39,6 +39,7 @@ namespace bs
 		~FBXImportNode();
 		~FBXImportNode();
 
 
 		Matrix4 geomTransform;
 		Matrix4 geomTransform;
+		Matrix4 localTransform;
 		Matrix4 worldTransform;
 		Matrix4 worldTransform;
 		String name;
 		String name;
 		FbxNode* fbxNode;
 		FbxNode* fbxNode;
@@ -70,6 +71,7 @@ namespace bs
 	struct FBXBone
 	struct FBXBone
 	{
 	{
 		FBXImportNode* node;
 		FBXImportNode* node;
+		Matrix4 localTfrm;
 		Matrix4 bindPose;
 		Matrix4 bindPose;
 	};
 	};
 
 

+ 33 - 9
Source/BansheeFBXImporter/BsFBXImporter.cpp

@@ -243,7 +243,7 @@ namespace bs
 		SPtr<RendererMeshData> rendererMeshData = generateMeshData(importedScene, fbxImportOptions, subMeshes);
 		SPtr<RendererMeshData> rendererMeshData = generateMeshData(importedScene, fbxImportOptions, subMeshes);
 
 
 		skeleton = createSkeleton(importedScene, subMeshes.size() > 1);
 		skeleton = createSkeleton(importedScene, subMeshes.size() > 1);
-		morphShapes = createMorphShapes(importedScene);		
+		morphShapes = createMorphShapes(importedScene);
 
 
 		// Import animation clips
 		// Import animation clips
 		if (!importedScene.clips.empty())
 		if (!importedScene.clips.empty())
@@ -276,6 +276,7 @@ namespace bs
 				BONE_DESC& bone = allBones.back();
 				BONE_DESC& bone = allBones.back();
 
 
 				bone.name = fbxBone.node->name;
 				bone.name = fbxBone.node->name;
+				bone.localTfrm = fbxBone.localTfrm;
 				bone.invBindPose = fbxBone.bindPose;
 				bone.invBindPose = fbxBone.bindPose;
 			}
 			}
 		}
 		}
@@ -296,6 +297,7 @@ namespace bs
 				BONE_DESC& bone = allBones.back();
 				BONE_DESC& bone = allBones.back();
 
 
 				bone.name = "MultiMeshRoot";
 				bone.name = "MultiMeshRoot";
+				bone.localTfrm = Matrix4::IDENTITY;
 				bone.invBindPose = Matrix4::IDENTITY;
 				bone.invBindPose = Matrix4::IDENTITY;
 				bone.parent = (UINT32)-1;
 				bone.parent = (UINT32)-1;
 
 
@@ -318,9 +320,26 @@ namespace bs
 				{
 				{
 					UINT32 boneIdx = boneIter->second;
 					UINT32 boneIdx = boneIter->second;
 					allBones[boneIdx].parent = parentBoneIdx;
 					allBones[boneIdx].parent = parentBoneIdx;
+
+					parentBoneIdx = boneIdx;
 					numProcessedBones++;
 					numProcessedBones++;
+				}
+				else
+				{
+					// Node is not a bone, but it still needs to be part of the hierarchy. It wont be animated, nor will
+					// it directly influence any vertices, but its transform must be applied to any child bones.
+					UINT32 boneIdx = (UINT32)allBones.size();
+
+					allBones.push_back(BONE_DESC());
+					BONE_DESC& bone = allBones.back();
+
+					bone.name = node->name;
+					bone.localTfrm = node->localTransform;
+					bone.invBindPose = Matrix4::IDENTITY;
+					bone.parent = parentBoneIdx;
 
 
 					parentBoneIdx = boneIdx;
 					parentBoneIdx = boneIdx;
+					numProcessedBones++;
 				}
 				}
 
 
 				for (auto& child : node->children)
 				for (auto& child : node->children)
@@ -596,18 +615,18 @@ namespace bs
 
 
 		Quaternion rotation((Degree)rotationEuler.x, (Degree)rotationEuler.y, (Degree)rotationEuler.z);
 		Quaternion rotation((Degree)rotationEuler.x, (Degree)rotationEuler.y, (Degree)rotationEuler.z);
 
 
-		Matrix4 localTransform = Matrix4::TRS(translation, rotation, scale);
 		node->name = fbxNode->GetNameWithoutNameSpacePrefix().Buffer();
 		node->name = fbxNode->GetNameWithoutNameSpacePrefix().Buffer();
 		node->fbxNode = fbxNode;
 		node->fbxNode = fbxNode;
+		node->localTransform = Matrix4::TRS(translation, rotation, scale);
 
 
 		if (parent != nullptr)
 		if (parent != nullptr)
 		{
 		{
-			node->worldTransform = parent->worldTransform * localTransform;
+			node->worldTransform = parent->worldTransform * node->localTransform;
 
 
 			parent->children.push_back(node);
 			parent->children.push_back(node);
 		}
 		}
 		else
 		else
-			node->worldTransform = localTransform;
+			node->worldTransform = node->localTransform;
 
 
 		// Geometry transform is applied to geometry (mesh data) only, it is not inherited by children, so we store it
 		// Geometry transform is applied to geometry (mesh data) only, it is not inherited by children, so we store it
 		// separately
 		// separately
@@ -1550,13 +1569,18 @@ namespace bs
 			}
 			}
 
 
 			// Calculate bind pose
 			// Calculate bind pose
-			FbxAMatrix clusterTransform;
-			cluster->GetTransformMatrix(clusterTransform);
-
+			////  Grab the transform of the node linked to this cluster (should be equivalent to bone.node->worldTransform)
 			FbxAMatrix linkTransform;
 			FbxAMatrix linkTransform;
 			cluster->GetTransformLinkMatrix(linkTransform);
 			cluster->GetTransformLinkMatrix(linkTransform);
 
 
-			FbxAMatrix invLinkTransform = linkTransform.Inverse() * clusterTransform;
+			//// A reminder about the cluster transform:
+			////	Cluster transform is the global transform applied to the mesh containing the cluster. Normally we would
+			////	need to apply it before the link transform to get the bind pose, but we bake this transform directly
+			////	into mesh vertices. The cluster transform can be retrieved through cluster->GetTransformMatrix, and 
+			////	should be equivalent to mesh.referencedBy[0]->worldTransform.
+
+			FbxAMatrix invLinkTransform = linkTransform.Inverse();
+			bone.localTfrm = bone.node->localTransform;
 			bone.bindPose = FBXToNativeType(invLinkTransform);
 			bone.bindPose = FBXToNativeType(invLinkTransform);
 
 
 			// Apply global scale to bind pose (we only apply the scale to translation portion because we scale the
 			// Apply global scale to bind pose (we only apply the scale to translation portion because we scale the
@@ -1614,7 +1638,7 @@ namespace bs
 				}
 				}
 			}
 			}
 		}
 		}
-
+		
 		if (mesh.bones.empty())
 		if (mesh.bones.empty())
 			mesh.boneInfluences.clear();
 			mesh.boneInfluences.clear();