فهرست منبع

Skeletal animation now functional:
- Euler angle -> quaternion transform now applies rotation in valid order (as documented)
- Fixed bind pose and animation scaling on FBX import
- Fixed animation time wrapping
- Fixing an issue with culling that prevented animation updates

BearishSun 9 سال پیش
والد
کامیت
8a65b3de98

+ 4 - 0
Source/BansheeCore/Include/BsAnimationUtility.h

@@ -30,6 +30,10 @@ namespace BansheeEngine
 
 
 		/** Converts a curve in quaternions into a curve using euler angles (in degrees). */
 		/** Converts a curve in quaternions into a curve using euler angles (in degrees). */
 		static TAnimationCurve<Vector3> quaternionToEulerCurve(const TAnimationCurve<Quaternion>& quatCurve);
 		static TAnimationCurve<Vector3> quaternionToEulerCurve(const TAnimationCurve<Quaternion>& quatCurve);
+
+		/** Scales all curve values and tangents by the specified scale factor. */
+		template<class T> 
+		static TAnimationCurve<T> scaleCurve(const TAnimationCurve<T>& curve, float factor);
 	};
 	};
 
 
 	/** @} */
 	/** @} */

+ 8 - 6
Source/BansheeCore/Source/BsAnimation.cpp

@@ -1025,6 +1025,14 @@ namespace BansheeEngine
 			clipInfo.fadeTime = Math::clamp(fadeTime, 0.0f, clipInfo.fadeLength);
 			clipInfo.fadeTime = Math::clamp(fadeTime, 0.0f, clipInfo.fadeLength);
 		}
 		}
 
 
+		if (mDirty.isSet(AnimDirtyStateFlag::Culling))
+		{
+			mAnimProxy->mCullEnabled = mCull;
+			mAnimProxy->mBounds = mBounds;
+
+			mDirty.unset(AnimDirtyStateFlag::Culling);
+		}
+
 		if((UINT32)mDirty == 0) // Clean
 		if((UINT32)mDirty == 0) // Clean
 		{
 		{
 			mAnimProxy->updateTime(mClipInfos);
 			mAnimProxy->updateTime(mClipInfos);
@@ -1059,12 +1067,6 @@ namespace BansheeEngine
 			else if (mDirty.isSet(AnimDirtyStateFlag::Value))
 			else if (mDirty.isSet(AnimDirtyStateFlag::Value))
 				mAnimProxy->updateValues(mClipInfos);
 				mAnimProxy->updateValues(mClipInfos);
 
 
-			if(mDirty.isSet(AnimDirtyStateFlag::Culling))
-			{
-				mAnimProxy->mCullEnabled = mCull;
-				mAnimProxy->mBounds = mBounds;
-			}
-
 			// Check if there are dirty transforms
 			// Check if there are dirty transforms
 			if(!didFullRebuild)
 			if(!didFullRebuild)
 			{
 			{

+ 2 - 2
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -190,9 +190,9 @@ namespace BansheeEngine
 		if(loop)
 		if(loop)
 		{
 		{
 			if (time < mStart)
 			if (time < mStart)
-				time = time - std::floor(time / mLength) * mLength;
+				time = time + (std::floor(mEnd - time) / mLength) * mLength;
 			else if (time > mEnd)
 			else if (time > mEnd)
-				time = time - std::floor(time / mLength) * mLength;
+				time = time - std::floor((time - mStart) / mLength) * mLength;
 		}
 		}
 
 
 		// If time is within cache, evaluate it directly
 		// If time is within cache, evaluate it directly

+ 31 - 4
Source/BansheeCore/Source/BsAnimationUtility.cpp

@@ -45,7 +45,7 @@ namespace BansheeEngine
 		if (time < start)
 		if (time < start)
 		{
 		{
 			if (loop)
 			if (loop)
-				time = time - std::floor(time / length) * length;
+				time = time + (std::floor(end - time) / length) * length;
 			else // Clamping
 			else // Clamping
 				time = start;
 				time = start;
 		}
 		}
@@ -54,7 +54,7 @@ namespace BansheeEngine
 		if (time > end)
 		if (time > end)
 		{
 		{
 			if (loop)
 			if (loop)
-				time = time - std::floor(time / length) * length;
+				time = time - std::floor((time - start) / length) * length;
 			else // Clamping
 			else // Clamping
 				time = end;
 				time = end;
 		}
 		}
@@ -62,8 +62,10 @@ namespace BansheeEngine
 
 
 	TAnimationCurve<Quaternion> AnimationUtility::eulerToQuaternionCurve(const TAnimationCurve<Vector3>& eulerCurve)
 	TAnimationCurve<Quaternion> AnimationUtility::eulerToQuaternionCurve(const TAnimationCurve<Vector3>& eulerCurve)
 	{
 	{
-		// TODO: We calculate tangents by sampling. There must be an analytical way to calculate tangents when converting
-		// a curve.
+		// TODO: We calculate tangents by sampling which can introduce error in the tangents. The error can be exacerbated
+		// by the fact we constantly switch between the two representations, possibly losing precision every time. Instead 
+		// there must be an analytical way to calculate tangents when converting a curve, or a better way of dealing with
+		// tangents.
 		// Consider: 
 		// Consider: 
 		//  - Sampling multiple points to calculate tangents to improve precision
 		//  - Sampling multiple points to calculate tangents to improve precision
 		//  - Store the original quaternion curve with the euler curve
 		//  - Store the original quaternion curve with the euler curve
@@ -72,6 +74,9 @@ namespace BansheeEngine
 		//		are converted between two formats back and forth.
 		//		are converted between two formats back and forth.
 		//  - Don't store rotation tangents directly, instead store tangent parameters (TCB) which can be shared between
 		//  - Don't store rotation tangents directly, instead store tangent parameters (TCB) which can be shared between
 		//    both curves, and used for tangent calculation.
 		//    both curves, and used for tangent calculation.
+		//
+		// If we decide to keep tangents in the current form, then we should also enforce that all euler curve tangents are
+		// the same.
 		const float FIT_TIME = 0.001f;
 		const float FIT_TIME = 0.001f;
 
 
 		auto eulerToQuaternion = [&](INT32 keyIdx, Vector3& angles, const Quaternion& lastQuat)
 		auto eulerToQuaternion = [&](INT32 keyIdx, Vector3& angles, const Quaternion& lastQuat)
@@ -211,4 +216,26 @@ namespace BansheeEngine
 
 
 		return TAnimationCurve<Vector3>(eulerKeyframes);
 		return TAnimationCurve<Vector3>(eulerKeyframes);
 	}
 	}
+
+	template<class T>
+	TAnimationCurve<T> AnimationUtility::scaleCurve(const TAnimationCurve<T>& curve, float factor)
+	{
+		INT32 numKeys = (INT32)curve.getNumKeyFrames();
+
+		Vector<TKeyframe<T>> newKeyframes(numKeys);
+		for (INT32 i = 0; i < numKeys; i++)
+		{
+			const TKeyframe<T>& key = curve.getKeyFrame(i);
+			newKeyframes[i].time = key.time;
+			newKeyframes[i].value = key.value * factor;
+			newKeyframes[i].inTangent = key.inTangent * factor;
+			newKeyframes[i].outTangent = key.outTangent * factor;
+		}
+
+		return TAnimationCurve<T>(newKeyframes);
+	}
+
+	template BS_CORE_EXPORT TAnimationCurve<Vector3> AnimationUtility::scaleCurve(const TAnimationCurve<Vector3>& curve, float factor);
+	template BS_CORE_EXPORT TAnimationCurve<Quaternion> AnimationUtility::scaleCurve(const TAnimationCurve<Quaternion>& curve, float factor);
+	template BS_CORE_EXPORT TAnimationCurve<float> AnimationUtility::scaleCurve(const TAnimationCurve<float>& curve, float factor);
 }
 }

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

@@ -166,6 +166,9 @@ namespace BansheeEngine
 		UnorderedMap<FbxMesh*, UINT32> meshMap;
 		UnorderedMap<FbxMesh*, UINT32> meshMap;
 
 
 		Vector<FBXAnimationClip> clips;
 		Vector<FBXAnimationClip> clips;
+
+		float scaleFactor;
+		Matrix4 globalScale;
 	};
 	};
 
 
 	/** @} */
 	/** @} */

+ 1 - 1
Source/BansheeFBXImporter/Source/BsFBXImportData.cpp

@@ -11,7 +11,7 @@ namespace BansheeEngine
 	}
 	}
 
 
 	FBXImportScene::FBXImportScene()
 	FBXImportScene::FBXImportScene()
-		:rootNode(nullptr)
+		:rootNode(nullptr), scaleFactor(1.0f), globalScale(Matrix4::IDENTITY)
 	{ }
 	{ }
 
 
 	FBXImportScene::~FBXImportScene()
 	FBXImportScene::~FBXImportScene()

+ 31 - 23
Source/BansheeFBXImporter/Source/BsFBXImporter.cpp

@@ -323,9 +323,11 @@ namespace BansheeEngine
 		if (options.importScale > 0.0001f)
 		if (options.importScale > 0.0001f)
 			importScale = options.importScale;
 			importScale = options.importScale;
 
 
-		FbxSystemUnit scaledMeters(100.0f / importScale);
-		scaledMeters.ConvertScene(scene);
+		FbxSystemUnit units = scene->GetGlobalSettings().GetSystemUnit();
+		FbxSystemUnit bsScaledUnits(100.0f, importScale);
 
 
+		outputScene.scaleFactor = (float)units.GetConversionFactorTo(bsScaledUnits);
+		outputScene.globalScale = Matrix4::scaling(outputScene.scaleFactor);
 		outputScene.rootNode = createImportNode(outputScene, scene->GetRootNode(), nullptr);
 		outputScene.rootNode = createImportNode(outputScene, scene->GetRootNode(), nullptr);
 
 
 		Stack<FbxNode*> todo;
 		Stack<FbxNode*> todo;
@@ -602,9 +604,9 @@ namespace BansheeEngine
 			UINT32 numIndices = (UINT32)mesh->indices.size();
 			UINT32 numIndices = (UINT32)mesh->indices.size();
 			for (auto& node : mesh->referencedBy)
 			for (auto& node : mesh->referencedBy)
 			{
 			{
-				Matrix4 worldTransform = node->worldTransform;
-				Matrix4 worldTransformIT = worldTransform.transpose();
-				worldTransformIT = worldTransformIT.inverse();
+				Matrix4 worldTransform = node->worldTransform * scene.globalScale;
+				Matrix4 worldTransformIT = worldTransform.inverse();
+				worldTransformIT = worldTransformIT.transpose();
 
 
 				SPtr<RendererMeshData> meshData = RendererMeshData::create((UINT32)numVertices, numIndices, (VertexLayout)vertexLayout);
 				SPtr<RendererMeshData> meshData = RendererMeshData::create((UINT32)numVertices, numIndices, (VertexLayout)vertexLayout);
 
 
@@ -1249,14 +1251,7 @@ namespace BansheeEngine
 		Vector<FBXBoneInfluence>& influences = mesh.boneInfluences;
 		Vector<FBXBoneInfluence>& influences = mesh.boneInfluences;
 		influences.resize(mesh.positions.size());
 		influences.resize(mesh.positions.size());
 
 
-		Matrix4 invBakedTransform;
-		if (mesh.referencedBy.size() > 0)
-		{
-			Matrix4 bakedTransform = mesh.referencedBy[0]->worldTransform;
-			invBakedTransform = bakedTransform.inverseAffine();
-		}
-		else
-			invBakedTransform = Matrix4::IDENTITY;
+		Matrix4 invGlobalScale = scene.globalScale.inverseAffine();
 
 
 		UnorderedSet<FbxNode*> existingBones;
 		UnorderedSet<FbxNode*> existingBones;
 		UINT32 boneCount = (UINT32)skin->GetClusterCount();
 		UINT32 boneCount = (UINT32)skin->GetClusterCount();
@@ -1275,15 +1270,6 @@ namespace BansheeEngine
 			FBXBone& bone = mesh.bones.back();
 			FBXBone& bone = mesh.bones.back();
 			bone.node = iterFind->second;
 			bone.node = iterFind->second;
 
 
-			FbxAMatrix clusterTransform;
-			cluster->GetTransformMatrix(clusterTransform);
-
-			FbxAMatrix linkTransform;
-			cluster->GetTransformLinkMatrix(linkTransform);
-
-			// For nodes attached to meshes we bake their transform directly into mesh vertices. We need to remove that
-			// transform
-
 			if(mesh.referencedBy.size() > 1)
 			if(mesh.referencedBy.size() > 1)
 			{
 			{
 				// Note: If this becomes a relevant issue (unlikely), then I will have to duplicate skeleton bones for
 				// Note: If this becomes a relevant issue (unlikely), then I will have to duplicate skeleton bones for
@@ -1293,8 +1279,29 @@ namespace BansheeEngine
 				LOGWRN("Skinned mesh has multiple different instances. This is not supported.");
 				LOGWRN("Skinned mesh has multiple different instances. This is not supported.");
 			}
 			}
 
 
+			// Calculate bind pose
+			FbxAMatrix clusterTransform;
+			cluster->GetTransformMatrix(clusterTransform);
+
+			FbxAMatrix linkTransform;
+			cluster->GetTransformLinkMatrix(linkTransform);
+
 			FbxAMatrix invLinkTransform = linkTransform.Inverse() * clusterTransform;
 			FbxAMatrix invLinkTransform = linkTransform.Inverse() * clusterTransform;
-			bone.bindPose = FBXToNativeType(invLinkTransform) * invBakedTransform;
+			bone.bindPose = FBXToNativeType(invLinkTransform);
+
+			// Apply global scale to bind pose (we only apply the scale to translation portion because we scale the
+			// translation animation curves)
+			const Matrix4& nodeTfrm = iterFind->second->worldTransform;
+
+			Matrix4 nodeTfrmScaledTranslation = nodeTfrm;
+			nodeTfrmScaledTranslation[0][3] = nodeTfrmScaledTranslation[0][3] / scene.scaleFactor;
+			nodeTfrmScaledTranslation[1][3] = nodeTfrmScaledTranslation[1][3] / scene.scaleFactor;
+			nodeTfrmScaledTranslation[2][3] = nodeTfrmScaledTranslation[2][3] / scene.scaleFactor;
+
+			Matrix4 nodeTfrmInv = nodeTfrm.inverseAffine();
+
+			Matrix4 scaledTranslation = nodeTfrmInv * scene.globalScale * nodeTfrmScaledTranslation;
+			bone.bindPose = scaledTranslation * bone.bindPose * invGlobalScale;
 
 
 			bool isDuplicate = !existingBones.insert(link).second;
 			bool isDuplicate = !existingBones.insert(link).second;
 			bool isAdditive = cluster->GetLinkMode() == FbxCluster::eAdditive;
 			bool isAdditive = cluster->GetLinkMode() == FbxCluster::eAdditive;
@@ -1508,6 +1515,7 @@ namespace BansheeEngine
 				eulerAnimation = reduceKeyframes(eulerAnimation);
 				eulerAnimation = reduceKeyframes(eulerAnimation);
 			}
 			}
 
 
+			boneAnim.translation = AnimationUtility::scaleCurve(boneAnim.translation, importScene.scaleFactor);
 			boneAnim.rotation = AnimationUtility::eulerToQuaternionCurve(eulerAnimation);
 			boneAnim.rotation = AnimationUtility::eulerToQuaternionCurve(eulerAnimation);
 		}
 		}
 
 

+ 1 - 1
Source/BansheeUtility/Source/BsMatrix3.cpp

@@ -846,7 +846,7 @@ namespace BansheeEngine
 			sz, cz, 0.0f,
 			sz, cz, 0.0f,
 			0.0f, 0.0f, 1.0f);
 			0.0f, 0.0f, 1.0f);
 	
 	
-		*this = mats[l.a]*(mats[l.b]*mats[l.c]);
+		*this = mats[l.c]*(mats[l.b]*mats[l.a]);
 	}
 	}
 
 
     void Matrix3::tridiagonal(float diag[3], float subDiag[3])
     void Matrix3::tridiagonal(float diag[3], float subDiag[3])

+ 2 - 2
Source/BansheeUtility/Source/BsQuaternion.cpp

@@ -111,7 +111,7 @@ namespace BansheeEngine
 		Quaternion quatY(cy, 0.0f, sy, 0.0f);
 		Quaternion quatY(cy, 0.0f, sy, 0.0f);
 		Quaternion quatZ(cz, 0.0f, 0.0f, sz);
 		Quaternion quatZ(cz, 0.0f, 0.0f, sz);
 
 
-		*this = (quatY * quatX) * quatZ;
+		*this = (quatZ * quatX) * quatY;
 	}
 	}
 
 
 	void Quaternion::fromEulerAngles(const Radian& xAngle, const Radian& yAngle, const Radian& zAngle, EulerAngleOrder order)
 	void Quaternion::fromEulerAngles(const Radian& xAngle, const Radian& yAngle, const Radian& zAngle, EulerAngleOrder order)
@@ -136,7 +136,7 @@ namespace BansheeEngine
 		quats[1] = Quaternion(cy, 0.0f, sy, 0.0f);
 		quats[1] = Quaternion(cy, 0.0f, sy, 0.0f);
 		quats[2] = Quaternion(cz, 0.0f, 0.0f, sz);
 		quats[2] = Quaternion(cz, 0.0f, 0.0f, sz);
 
 
-		*this = (quats[l.a] * quats[l.b]) * quats[l.c];
+		*this = (quats[l.c] * quats[l.b]) * quats[l.a];
 	}
 	}
 
 
 	void Quaternion::toRotationMatrix(Matrix3& mat) const
 	void Quaternion::toRotationMatrix(Matrix3& mat) const

+ 1 - 1
Source/CMakeLists.txt

@@ -93,7 +93,7 @@ if(MSVC)
 	endif()
 	endif()
 	
 	
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} /GL /Gy /Zi /Gm /O2 /Oi /MD")
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} /GL /Gy /Zi /Gm /O2 /Oi /MD")
-	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} /GL /Gy /O2 /Oi /MD /MP")
+	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} /GL /Gy /O2 /Oi /MD")
 	
 	
 	# Global defines
 	# Global defines
 	add_definitions(-D_HAS_EXCEPTIONS=0)
 	add_definitions(-D_HAS_EXCEPTIONS=0)