You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4d4196d
Browse filesBrowse the repository at this point in the historyBrowse files
Fix quaternion slerp when the endpoints are the same rotation
The slerp weights sin((1-s)t)/sin(t) and sin(s.t)/sin(t) are singular wherever
sin(t) vanishes, ie. at t=0 (coincident endpoints) and t=pi (antipodal endpoints,
which are the same rotation under the double cover). qslerp() guarded only t=0;
UnitQuaternion.interp() and .interp1() re-derived the weights inline and guarded
neither, so they raised on valid input:
q = UnitQuaternion.Rx(0.3)
q.interp(q, 0.5) # ZeroDivisionError
UnitQuaternion().interp1(0.5) # ZeroDivisionError
UnitQuaternion.Rx(pi).interp(UnitQuaternion.Rx(-pi), 0.5, shortest=True)
# ZeroDivisionError
UnitQuaternion.Rx(pi).interp(UnitQuaternion.Rx(-pi), 5)
# TypeError, non-unit result
qslerp() itself returned non-unit quaternions near t=pi: qslerp(q, -q, 0.5)
gave [0 0 0 0], and for endpoints 1e-9 from antipodal the norm reached 5.8e6.
Root of that: acos(dotprod) loses the small angle to rounding at both ends, so
sin(acos(dotprod)) is a bad denominator. Take sin(t) directly as the length of
the component of q1 orthogonal to q0, which keeps full relative precision, and
get t from atan2. The only remaining degenerate case is sin(t) == 0, where q0
and q1 are the same rotation and so is every interpolate.
Measured against a 50-digit q0*exp(s*log(q0^-1.q1)) reference over the whole
range of t and s, worst error for t within 1e-6 of pi drops from 4.4e-5 to
2.7e-10 rad, and the largest deviation from unit norm anywhere from 8.2e6 to
2.8e-4. Ordinary angles are unchanged to within 1 ulp, and the whole surface
still agrees with scipy's Slerp to 1.4e-15 rad over 10000 random pairs.
The two UnitQuaternion methods now call qslerp(), which their own :seealso:
already pointed at, so the formula lives in one place.
0 commit comments