2
0
Эх сурвалжийг харах

Generate tangents for quaternion animation curves during FBX import

Marko Pintera 10 жил өмнө
parent
commit
deaf32cf4f

+ 106 - 13
BansheeFBXImporter/Source/BsFBXImporter.cpp

@@ -1226,30 +1226,64 @@ namespace BansheeEngine
 
 
 	void FBXImporter::eulerToQuaternionCurves(FBXAnimationCurve(&eulerCurves)[3], FBXAnimationCurve(&quatCurves)[4])
 	void FBXImporter::eulerToQuaternionCurves(FBXAnimationCurve(&eulerCurves)[3], FBXAnimationCurve(&quatCurves)[4])
 	{
 	{
+		const float FIT_TIME = 0.33f;
+
 		INT32 numKeys = (INT32)eulerCurves[0].keyframes.size();
 		INT32 numKeys = (INT32)eulerCurves[0].keyframes.size();
 
 
 		if (numKeys != (INT32)eulerCurves[1].keyframes.size() || numKeys != (INT32)eulerCurves[2].keyframes.size())
 		if (numKeys != (INT32)eulerCurves[1].keyframes.size() || numKeys != (INT32)eulerCurves[2].keyframes.size())
 			return;
 			return;
 
 
-		Quaternion lastQuat;
-		for (INT32 i = 0; i < numKeys; i++)
+		auto eulerToQuaternion = [&](INT32 keyIdx, float time, const Quaternion& lastQuat)
 		{
 		{
-			float time = eulerCurves[0].keyframes[i].time;
-
-			Degree x = (Degree)eulerCurves[0].keyframes[i].value;
-			Degree y = (Degree)eulerCurves[1].keyframes[i].value;
-			Degree z = (Degree)eulerCurves[2].keyframes[i].value;
+			Degree x = (Degree)eulerCurves[0].evaluate(time);
+			Degree y = (Degree)eulerCurves[1].evaluate(time);
+			Degree z = (Degree)eulerCurves[2].evaluate(time);
 
 
 			Quaternion quat(x, y, z);
 			Quaternion quat(x, y, z);
 
 
 			// Flip quaternion in case rotation is over 180 degrees
 			// Flip quaternion in case rotation is over 180 degrees
-			if (i > 0)
+			if (keyIdx > 0)
 			{
 			{
 				float dot = quat.dot(lastQuat);
 				float dot = quat.dot(lastQuat);
 				if (dot < 0.0f)
 				if (dot < 0.0f)
 					quat = Quaternion(-quat.x, -quat.y, -quat.z, -quat.w);
 					quat = Quaternion(-quat.x, -quat.y, -quat.z, -quat.w);
 			}
 			}
 
 
+			return quat;
+		};
+
+		struct FitKeyframe
+		{
+			float time;
+			Quaternion value;
+		};
+
+		Vector<FitKeyframe> fitQuaternions(numKeys * 2);
+
+		Quaternion lastQuat;
+		for (INT32 i = 0; i < numKeys; i++)
+		{
+			float time = eulerCurves[0].keyframes[i].time;
+			Quaternion quat = eulerToQuaternion(i, time, lastQuat);
+
+			// Calculate extra values between keys so we can better approximate tangents
+			if ((i + 1) < numKeys)
+			{
+				float nextTime = eulerCurves[0].keyframes[i + 1].time;
+				float dt = nextTime - time;
+
+				FitKeyframe& fitStart = fitQuaternions[i * 2 + 0];
+				FitKeyframe& fitEnd = fitQuaternions[i * 2 + 1];
+
+				fitStart.time = time + dt * FIT_TIME;
+				fitEnd.time = time + dt * (1.0f - FIT_TIME);
+
+				fitStart.value = eulerToQuaternion(i, fitStart.time, quat);
+				fitEnd.value = eulerToQuaternion(i, fitEnd.time, fitStart.value);
+
+				lastQuat = fitStart.value;
+			}
+
 			// TODO - If animation is looping I should also compare last and first for continuity
 			// TODO - If animation is looping I should also compare last and first for continuity
 
 
 			for (INT32 j = 0; j < 4; j++)
 			for (INT32 j = 0; j < 4; j++)
@@ -1259,15 +1293,74 @@ namespace BansheeEngine
 				keyFrame.time = time;
 				keyFrame.time = time;
 				keyFrame.value = quat[j];
 				keyFrame.value = quat[j];
 
 
-				// TODO - Recalculate tangents(make sure not to ignore original ones)
-				//  - Pay attention to deal with non-equally spaced keyframes
-				// TODO - Tangent recalculation assumes all curves are cubic, which might not be the case
-
 				keyFrame.inTangent = 0;
 				keyFrame.inTangent = 0;
 				keyFrame.outTangent = 0;
 				keyFrame.outTangent = 0;
 			}
 			}
+		}
+
+		// Recalculate tangents for quaternion curves
+
+		// TODO - There must be an analytical way to convert euler angle tangents
+		//        to quaternion tangents, but I don't want to bother figuring it out
+		//        until I have a test-bed for animation.
+		if (numKeys > 1)
+		{
+			// TODO - I could check per-key curve interpolation originally assigned in FBX
+			//        and use that to generate linear/constant slopes. Currently I assume 
+			//        its all cubic.
+
+			// First key
+			{
+				const FitKeyframe& fitKeyFrame = fitQuaternions[0];
+
+				for (INT32 j = 0; j < 4; j++)
+				{
+					FBXKeyFrame& keyFrame = quatCurves[j].keyframes[0];
+
+					float dt = fitKeyFrame.time - keyFrame.time;
+
+					keyFrame.inTangent = (fitKeyFrame.value[j] - keyFrame.value) / dt;
+					keyFrame.outTangent = keyFrame.inTangent;
+				}
+			}
+
+			// In-between keys
+			{
+				for (INT32 i = 1; i < (numKeys - 1); i++)
+				{
+					const FitKeyframe& fitPointStart = fitQuaternions[i * 2 - 1];
+					const FitKeyframe& fitPointEnd = fitQuaternions[i * 2 + 0];
+
+					for (INT32 j = 0; j < 4; j++)
+					{
+						FBXKeyFrame& keyFrame = quatCurves[j].keyframes[i];
 
 
-			lastQuat = quat;
+						float dt0 = fitPointEnd.time - keyFrame.time;
+						float dt1 = keyFrame.time - fitPointStart.time;
+
+						float t0 = fitPointEnd.value[j] - keyFrame.value;
+						float t1 = keyFrame.value - fitPointStart.value[j];
+
+						keyFrame.inTangent = t0 / (0.5f * dt0) + t1 / (0.5f * dt1);
+						keyFrame.outTangent = keyFrame.inTangent;
+					}
+				}
+			}
+
+			// Last key
+			{
+				const FitKeyframe& fitKeyFrame = fitQuaternions[(numKeys - 2) * 2];
+
+				for (INT32 j = 0; j < 4; j++)
+				{
+					FBXKeyFrame& keyFrame = quatCurves[j].keyframes[numKeys - 2];
+
+					float dt = keyFrame.time - fitKeyFrame.time;
+
+					keyFrame.inTangent = (keyFrame.value - fitKeyFrame.value[j]) / dt;
+					keyFrame.outTangent = keyFrame.inTangent;
+				}
+			}
 		}
 		}
 	}
 	}
 
 

+ 0 - 1
TODOExperimentation.txt

@@ -3,5 +3,4 @@ FBX
 Next:
 Next:
  - DefaultMeshData is defined in BansheeEngine but FBXImporter only includes Banshee Core
  - DefaultMeshData is defined in BansheeEngine but FBXImporter only includes Banshee Core
   - Therefore I am not using it in FBXImporter but I should
   - Therefore I am not using it in FBXImporter but I should
- - When import animation generate slopes for quaternion rotation curves