Skip to content

Commit 22518fd

Browse files
committed
Inline prepared slerp loops
1 parent 75856e8 commit 22518fd

2 files changed

Lines changed: 65 additions & 28 deletions

File tree

‎spatialmath/base/quaternions.py‎

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from spatialmath.base.argcheck import getunit
1818
from spatialmath.base.types import *
1919
import scipy.interpolate as interpolate
20-
from typing import Callable, Optional
20+
from typing import Optional
2121
from functools import lru_cache
2222
import 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

821815
def 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

875882
def _compute_cdf_sin_squared(theta: float):

‎spatialmath/quaternion.py‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import numpy as np
2020
from typing import Any
2121
import spatialmath.base as smb
22-
from spatialmath.base.quaternions import _qslerp
22+
from spatialmath.base.quaternions import _qslerp_prepare
2323
from spatialmath.pose3d import SO3, SE3
2424
from spatialmath.baseposelist import BasePoseList
2525
from spatialmath.base.types import *
@@ -1951,8 +1951,23 @@ def interp(
19511951
if not isinstance(end, UnitQuaternion):
19521952
raise TypeError("end argument must be a UnitQuaternion")
19531953

1954-
interpolate = _qslerp(self.vec, end.vec, shortest=shortest)
1955-
return UnitQuaternion([interpolate(sk) for sk in s])
1954+
q0_endpoint, q0, q1, sin_theta, theta = _qslerp_prepare(
1955+
self.vec, end.vec, shortest=shortest
1956+
)
1957+
qi = []
1958+
for sk in s:
1959+
if sk == 0:
1960+
out = q0_endpoint
1961+
elif sk == 1:
1962+
out = q1
1963+
elif sin_theta > 20 * _eps:
1964+
s0 = math.sin((1 - sk) * theta)
1965+
s1 = math.sin(sk * theta)
1966+
out = ((q0 * s0) + (q1 * s1)) / sin_theta
1967+
else:
1968+
out = q0
1969+
qi.append(out)
1970+
return UnitQuaternion(qi)
19561971

19571972
def interp1(self, s: float = 0, shortest: Optional[bool] = False) -> UnitQuaternion:
19581973
"""
@@ -1999,8 +2014,23 @@ def interp1(self, s: float = 0, shortest: Optional[bool] = False) -> UnitQuatern
19992014
s = smb.getvector(s)
20002015
s = np.clip(s, 0, 1) # enforce valid values
20012016

2002-
interpolate = _qslerp(smb.qeye(), self.vec, shortest=shortest)
2003-
return UnitQuaternion([interpolate(sk) for sk in s])
2017+
q0_endpoint, q0, q1, sin_theta, theta = _qslerp_prepare(
2018+
smb.qeye(), self.vec, shortest=shortest
2019+
)
2020+
qi = []
2021+
for sk in s:
2022+
if sk == 0:
2023+
out = q0_endpoint
2024+
elif sk == 1:
2025+
out = q1
2026+
elif sin_theta > 20 * _eps:
2027+
s0 = math.sin((1 - sk) * theta)
2028+
s1 = math.sin(sk * theta)
2029+
out = ((q0 * s0) + (q1 * s1)) / sin_theta
2030+
else:
2031+
out = q0
2032+
qi.append(out)
2033+
return UnitQuaternion(qi)
20042034

20052035
def increment(self, w: ArrayLike3, normalize: Optional[bool] = False) -> None:
20062036
"""

0 commit comments

Comments
 (0)