1717from spatialmath .base .argcheck import getunit
1818from spatialmath .base .types import *
1919import scipy .interpolate as interpolate
20- from typing import Callable , Optional
20+ from typing import Optional
2121from functools import lru_cache
2222import warnings
2323
@@ -771,13 +771,23 @@ def r2q(
771771# return np.r_[qs, (math.sqrt(1.0 - qs**2) / nm) * kv]
772772
773773
774- def _qslerp (
774+ def _qslerp_prepare (
775775 q0 : ArrayLike4 ,
776776 q1 : ArrayLike4 ,
777777 shortest : Optional [bool ] = False ,
778- tol : float = 20 ,
779- ) -> Callable [[float ], UnitQuaternionArray ]:
780- """Prepare an interpolator for a pair of unit quaternions."""
778+ ) -> tuple [
779+ UnitQuaternionArray ,
780+ UnitQuaternionArray ,
781+ UnitQuaternionArray ,
782+ float ,
783+ float ,
784+ ]:
785+ """Compute the loop-invariant slerp terms for two unit quaternions.
786+
787+ The original ``q0`` endpoint is returned separately from the sign-adjusted
788+ value used by shortest-path interpolation, preserving the exact value at
789+ ``s=0``.
790+ """
781791 q0 = smb .getvector (q0 , 4 )
782792 q1 = smb .getvector (q1 , 4 )
783793 q0_endpoint = q0
@@ -799,23 +809,7 @@ def _qslerp(
799809 # sin(acos(dotprod)) does not: acos loses the small angle to rounding.
800810 sin_theta = float (np .linalg .norm (q1 - dotprod * q0 ))
801811 theta = math .atan2 (sin_theta , dotprod ) # theta is the angle between q0 and q1
802-
803- def interpolate (s : float ) -> UnitQuaternionArray :
804- if s == 0 :
805- return q0_endpoint
806- elif s == 1 :
807- return q1
808-
809- if sin_theta > tol * _eps :
810- s0 = math .sin ((1 - s ) * theta )
811- s1 = math .sin (s * theta )
812- return ((q0 * s0 ) + (q1 * s1 )) / sin_theta
813- else :
814- # theta is 0 or pi: q0 and q1 are the same rotation, so is every
815- # interpolate between them
816- return q0
817-
818- return interpolate
812+ return q0_endpoint , q0 , q1 , sin_theta , theta
819813
820814
821815def qslerp (
@@ -869,7 +863,20 @@ def qslerp(
869863 """
870864 if not 0 <= s <= 1 :
871865 raise ValueError ("s must be in the interval [0,1]" )
872- return _qslerp (q0 , q1 , shortest = shortest , tol = tol )(s )
866+ q0_endpoint , q0 , q1 , sin_theta , theta = _qslerp_prepare (q0 , q1 , shortest = shortest )
867+ if s == 0 :
868+ return q0_endpoint
869+ elif s == 1 :
870+ return q1
871+
872+ if sin_theta > tol * _eps :
873+ s0 = math .sin ((1 - s ) * theta )
874+ s1 = math .sin (s * theta )
875+ return ((q0 * s0 ) + (q1 * s1 )) / sin_theta
876+ else :
877+ # theta is 0 or pi: q0 and q1 are the same rotation, so is every
878+ # interpolate between them
879+ return q0
873880
874881
875882def _compute_cdf_sin_squared (theta : float ):
0 commit comments