Browse Source

Merge pull request #63593 from TokageItLab/improve-exp-map

Rémi Verschelde 3 years ago
parent
commit
5ca09b322c
1 changed files with 28 additions and 24 deletions
  1. 28 24
      core/math/quaternion.cpp

+ 28 - 24
core/math/quaternion.cpp

@@ -188,45 +188,49 @@ Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const
 	ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized.");
 	ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized.");
 #endif
-	Quaternion ret_q = *this;
+	Quaternion from_q = *this;
 	Quaternion pre_q = p_pre_a;
 	Quaternion to_q = p_b;
 	Quaternion post_q = p_post_b;
 
 	// Align flip phases.
-	ret_q = Basis(ret_q).get_rotation_quaternion();
+	from_q = Basis(from_q).get_rotation_quaternion();
 	pre_q = Basis(pre_q).get_rotation_quaternion();
 	to_q = Basis(to_q).get_rotation_quaternion();
 	post_q = Basis(post_q).get_rotation_quaternion();
 
 	// Flip quaternions to shortest path if necessary.
-	bool flip1 = signbit(ret_q.dot(pre_q));
+	bool flip1 = signbit(from_q.dot(pre_q));
 	pre_q = flip1 ? -pre_q : pre_q;
-	bool flip2 = signbit(ret_q.dot(to_q));
+	bool flip2 = signbit(from_q.dot(to_q));
 	to_q = flip2 ? -to_q : to_q;
 	bool flip3 = flip2 ? to_q.dot(post_q) <= 0 : signbit(to_q.dot(post_q));
 	post_q = flip3 ? -post_q : post_q;
 
-	if (flip1 || flip2 || flip3) {
-		// Angle is too large, calc by Approximate.
-		ret_q.x = Math::cubic_interpolate(ret_q.x, to_q.x, pre_q.x, post_q.x, p_weight);
-		ret_q.y = Math::cubic_interpolate(ret_q.y, to_q.y, pre_q.y, post_q.y, p_weight);
-		ret_q.z = Math::cubic_interpolate(ret_q.z, to_q.z, pre_q.z, post_q.z, p_weight);
-		ret_q.w = Math::cubic_interpolate(ret_q.w, to_q.w, pre_q.w, post_q.w, p_weight);
-		ret_q.normalize();
-	} else {
-		// Calc by Expmap.
-		Quaternion ln_ret = ret_q.log();
-		Quaternion ln_to = to_q.log();
-		Quaternion ln_pre = pre_q.log();
-		Quaternion ln_post = post_q.log();
-		Quaternion ln = Quaternion(0, 0, 0, 0);
-		ln.x = Math::cubic_interpolate(ln_ret.x, ln_to.x, ln_pre.x, ln_post.x, p_weight);
-		ln.y = Math::cubic_interpolate(ln_ret.y, ln_to.y, ln_pre.y, ln_post.y, p_weight);
-		ln.z = Math::cubic_interpolate(ln_ret.z, ln_to.z, ln_pre.z, ln_post.z, p_weight);
-		ret_q = ln.exp();
-	}
-	return ret_q;
+	// Calc by Expmap in from_q space.
+	Quaternion ln_from = Quaternion(0, 0, 0, 0);
+	Quaternion ln_to = (from_q.inverse() * to_q).log();
+	Quaternion ln_pre = (from_q.inverse() * pre_q).log();
+	Quaternion ln_post = (from_q.inverse() * post_q).log();
+	Quaternion ln = Quaternion(0, 0, 0, 0);
+	ln.x = Math::cubic_interpolate(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight);
+	ln.y = Math::cubic_interpolate(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight);
+	ln.z = Math::cubic_interpolate(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight);
+	Quaternion q1 = from_q * ln.exp();
+
+	// Calc by Expmap in to_q space.
+	ln_from = (to_q.inverse() * from_q).log();
+	ln_to = Quaternion(0, 0, 0, 0);
+	ln_pre = (to_q.inverse() * pre_q).log();
+	ln_post = (to_q.inverse() * post_q).log();
+	ln = Quaternion(0, 0, 0, 0);
+	ln.x = Math::cubic_interpolate(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight);
+	ln.y = Math::cubic_interpolate(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight);
+	ln.z = Math::cubic_interpolate(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight);
+	Quaternion q2 = to_q * ln.exp();
+
+	// To cancel error made by Expmap ambiguity, do blends.
+	return q1.slerp(q2, p_weight);
 }
 
 Quaternion::operator String() const {