Browse Source

Fixed possible division by zero in Quat::sFromTo (#401)

This could be triggered from SkeletonMapper::Map in case a chain has zero length
Jorrit Rouwe 2 years ago
parent
commit
947874485b
2 changed files with 41 additions and 3 deletions
  1. 14 3
      Jolt/Math/Quat.inl
  2. 27 0
      UnitTests/Math/QuatTests.cpp

+ 14 - 3
Jolt/Math/Quat.inl

@@ -128,11 +128,22 @@ Quat Quat::sFromTo(Vec3Arg inFrom, Vec3Arg inTo)
 		which then needs to be normalized because the whole equation was multiplied by 2 cos(angle / 2)
 		which then needs to be normalized because the whole equation was multiplied by 2 cos(angle / 2)
 	*/
 	*/
 
 
-	float w = sqrt(inFrom.LengthSq() * inTo.LengthSq()) + inFrom.Dot(inTo);
+	float len_v1_v2 = sqrt(inFrom.LengthSq() * inTo.LengthSq());
+	float w = len_v1_v2 + inFrom.Dot(inTo);
 
 
-	// Check if vectors are perpendicular, if take one of the many 180 degree rotations that exist
 	if (w == 0.0f)
 	if (w == 0.0f)
-		return Quat(Vec4(inFrom.GetNormalizedPerpendicular(), 0));
+	{
+		if (len_v1_v2 == 0.0f)
+		{
+			// If either of the vectors has zero length, there is no rotation and we return identity
+			return Quat::sIdentity();
+		}
+		else
+		{
+			// If vectors are perpendicular, take one of the many 180 degree rotations that exist	
+			return Quat(Vec4(inFrom.GetNormalizedPerpendicular(), 0));
+		}
+	}
 
 
 	Vec3 v = inFrom.Cross(inTo);
 	Vec3 v = inFrom.Cross(inTo);
 	return Quat(Vec4(v, w)).Normalized();
 	return Quat(Vec4(v, w)).Normalized();

+ 27 - 0
UnitTests/Math/QuatTests.cpp

@@ -370,6 +370,7 @@ TEST_SUITE("QuatTests")
 	TEST_CASE("TestQuatRotationFromTo")
 	TEST_CASE("TestQuatRotationFromTo")
 	{
 	{
 		{
 		{
+			// Parallel vectors
 			Vec3 v1(10, 0, 0);
 			Vec3 v1(10, 0, 0);
 			Vec3 v2(20, 0, 0);
 			Vec3 v2(20, 0, 0);
 			Quat q = Quat::sFromTo(v1, v2);
 			Quat q = Quat::sFromTo(v1, v2);
@@ -377,6 +378,7 @@ TEST_SUITE("QuatTests")
 		}
 		}
 
 
 		{
 		{
+			// Perpendicular vectors
 			Vec3 v1(10, 0, 0);
 			Vec3 v1(10, 0, 0);
 			Vec3 v2(0, 20, 0);
 			Vec3 v2(0, 20, 0);
 			Quat q = Quat::sFromTo(v1, v2);
 			Quat q = Quat::sFromTo(v1, v2);
@@ -384,11 +386,36 @@ TEST_SUITE("QuatTests")
 		}
 		}
 
 
 		{
 		{
+			// Vectors with 180 degree angle
 			Vec3 v1(10, 0, 0);
 			Vec3 v1(10, 0, 0);
 			Vec3 v2(-20, 0, 0);
 			Vec3 v2(-20, 0, 0);
 			Quat q = Quat::sFromTo(v1, v2);
 			Quat q = Quat::sFromTo(v1, v2);
 			CHECK_APPROX_EQUAL(v2.Normalized(), (q * v1).Normalized());
 			CHECK_APPROX_EQUAL(v2.Normalized(), (q * v1).Normalized());
 		}
 		}
+
+		{
+			// Test v1 zero
+			Vec3 v1 = Vec3::sZero();
+			Vec3 v2(10, 0, 0);
+			Quat q = Quat::sFromTo(v1, v2);
+			CHECK(q == Quat::sIdentity());
+		}
+
+		{
+			// Test v2 zero
+			Vec3 v1(10, 0, 0);
+			Vec3 v2 = Vec3::sZero();
+			Quat q = Quat::sFromTo(v1, v2);
+			CHECK(q == Quat::sIdentity());
+		}
+
+		{
+			// Length of a vector is squared inside the function: try with sqrt(FLT_MIN) to see if that still returns a valid rotation
+			Vec3 v1(0, sqrt(FLT_MIN), 0);
+			Vec3 v2(1, 0, 0);
+			Quat q = Quat::sFromTo(v1, v2);
+			CHECK_APPROX_EQUAL(v2.Normalized(), (q * v1).Normalized());
+		}
 	}
 	}
 
 
 	TEST_CASE("TestQuatRotationFromToRandom")
 	TEST_CASE("TestQuatRotationFromToRandom")