diff options
author | Silc Renew <tokage.it.lab@gmail.com> | 2022-07-29 04:55:10 +0900 |
---|---|---|
committer | Silc Renew <tokage.it.lab@gmail.com> | 2022-07-29 17:39:22 +0900 |
commit | 90dc2f961e0016b9dac21258ef0cdc7912bf2dcd (patch) | |
tree | a74c53ca0c55cfab6e94f4a4dc6be567494f7b6c /core/math | |
parent | edb503cd00614391d14759777c38d8be6d0f20aa (diff) |
Make `spherical_cubic_interpolate()` more stable
Diffstat (limited to 'core/math')
-rw-r--r-- | core/math/quaternion.cpp | 52 |
1 files changed, 28 insertions, 24 deletions
diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index 252c108166..c681c60694 100644 --- a/core/math/quaternion.cpp +++ b/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 { |