|
17 | 17 | from spatialmath.base.argcheck import getunit |
18 | 18 | from spatialmath.base.types import * |
19 | 19 | import scipy.interpolate as interpolate |
20 | | -from typing import Optional |
| 20 | +from typing import Callable, Optional |
21 | 21 | from functools import lru_cache |
22 | 22 | import warnings |
23 | 23 |
|
@@ -771,6 +771,53 @@ def r2q( |
771 | 771 | # return np.r_[qs, (math.sqrt(1.0 - qs**2) / nm) * kv] |
772 | 772 |
|
773 | 773 |
|
| 774 | +def _qslerp( |
| 775 | + q0: ArrayLike4, |
| 776 | + q1: ArrayLike4, |
| 777 | + shortest: Optional[bool] = False, |
| 778 | + tol: float = 20, |
| 779 | +) -> Callable[[float], UnitQuaternionArray]: |
| 780 | + """Prepare an interpolator for a pair of unit quaternions.""" |
| 781 | + q0 = smb.getvector(q0, 4) |
| 782 | + q1 = smb.getvector(q1, 4) |
| 783 | + q0_endpoint = q0 |
| 784 | + |
| 785 | + dotprod = np.dot(q0, q1) |
| 786 | + |
| 787 | + # If the dot product is negative, the quaternions |
| 788 | + # have opposite handed-ness and slerp won't take |
| 789 | + # the shorter path. Fix by reversing one quaternion. |
| 790 | + if shortest: |
| 791 | + if dotprod < 0: |
| 792 | + q0 = -q0 # pylint: disable=invalid-unary-operand-type |
| 793 | + dotprod = -dotprod # pylint: disable=invalid-unary-operand-type |
| 794 | + |
| 795 | + dotprod = np.clip(dotprod, -1, 1) |
| 796 | + |
| 797 | + # sin(theta) is the length of the component of q1 orthogonal to q0. Computing |
| 798 | + # it this way keeps full relative precision as theta approaches 0 or pi, where |
| 799 | + # sin(acos(dotprod)) does not: acos loses the small angle to rounding. |
| 800 | + sin_theta = float(np.linalg.norm(q1 - dotprod * q0)) |
| 801 | + 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 |
| 819 | + |
| 820 | + |
774 | 821 | def qslerp( |
775 | 822 | q0: ArrayLike4, |
776 | 823 | q1: ArrayLike4, |
@@ -822,40 +869,7 @@ def qslerp( |
822 | 869 | """ |
823 | 870 | if not 0 <= s <= 1: |
824 | 871 | raise ValueError("s must be in the interval [0,1]") |
825 | | - q0 = smb.getvector(q0, 4) |
826 | | - q1 = smb.getvector(q1, 4) |
827 | | - |
828 | | - if s == 0: |
829 | | - return q0 |
830 | | - elif s == 1: |
831 | | - return q1 |
832 | | - |
833 | | - dotprod = np.dot(q0, q1) |
834 | | - |
835 | | - # If the dot product is negative, the quaternions |
836 | | - # have opposite handed-ness and slerp won't take |
837 | | - # the shorter path. Fix by reversing one quaternion. |
838 | | - if shortest: |
839 | | - if dotprod < 0: |
840 | | - q0 = -q0 # pylint: disable=invalid-unary-operand-type |
841 | | - dotprod = -dotprod # pylint: disable=invalid-unary-operand-type |
842 | | - |
843 | | - dotprod = np.clip(dotprod, -1, 1) # Clip within domain of acos() |
844 | | - |
845 | | - # sin(theta) is the length of the component of q1 orthogonal to q0. Computing |
846 | | - # it this way keeps full relative precision as theta approaches 0 or pi, where |
847 | | - # sin(acos(dotprod)) does not: acos loses the small angle to rounding. |
848 | | - sin_theta = float(np.linalg.norm(q1 - dotprod * q0)) |
849 | | - theta = math.atan2(sin_theta, dotprod) # theta is the angle between q0 and q1 |
850 | | - |
851 | | - if sin_theta > tol * _eps: |
852 | | - s0 = math.sin((1 - s) * theta) |
853 | | - s1 = math.sin(s * theta) |
854 | | - return ((q0 * s0) + (q1 * s1)) / sin_theta |
855 | | - else: |
856 | | - # theta is 0 or pi: q0 and q1 are the same rotation, so is every |
857 | | - # interpolate between them |
858 | | - return q0 |
| 872 | + return _qslerp(q0, q1, shortest=shortest, tol=tol)(s) |
859 | 873 |
|
860 | 874 |
|
861 | 875 | def _compute_cdf_sin_squared(theta: float): |
|
0 commit comments