1 /*
2 Copyright (c) 2011-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * Quaternions
31  *
32  * Copyright: Timur Gafarov 2011-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov
35  */
36 module dlib.math.quaternion;
37 
38 import std.math;
39 import std.traits;
40 
41 import dlib.math.vector;
42 import dlib.math.matrix;
43 import dlib.math.utils;
44 
45 /**
46  * Quaternion representation
47  */
48 struct Quaternion(T)
49 {
50     Vector!(T,4) vectorof;
51     alias vectorof this;
52 
53     this(T x, T y, T z, T w)
54     {
55         vectorof = Vector!(T,4)(x, y, z, w);
56     }
57 
58     this(T[4] arr)
59     {
60         vectorof.arrayof = arr;
61     }
62 
63     this(Vector!(T,4) v)
64     {
65         vectorof = v;
66     }
67 
68     this(Vector!(T,3) v, T neww)
69     {
70         vectorof = Vector!(T,4)(v.x, v.y, v.z, neww);
71     }
72 
73    /**
74     * Identity quaternion
75     */
76     static Quaternion!(T) identity()
77     {
78         return Quaternion!(T)(T(0), T(0), T(0), T(1));
79     }
80 
81    /**
82     * Quaternion!(T) + Quaternion!(T)
83     */
84     Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "+")
85     {
86         return Quaternion!(T)(x + q.x, y + q.y, z + q.z, w + q.w);
87     }
88 
89    /**
90     * Quaternion!(T) += Quaternion!(T)
91     */
92     Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "+")
93     {
94         this = this + q;
95         return this;
96     }
97 
98    /**
99     * Quaternion!(T) - Quaternion!(T)
100     */
101     Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "-")
102     {
103         return Quaternion!(T)(x - q.x, y - q.y, z - q.z, w - q.w);
104     }
105 
106    /**
107     * Quaternion!(T) -= Quaternion!(T)
108     */
109     Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "-")
110     {
111         this = this - q;
112         return this;
113     }
114 
115    /**
116     * Quaternion!(T) * Quaternion!(T)
117     */
118     Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "*")
119     {
120         return Quaternion!(T)
121         (
122             (x * q.w) + (w * q.x) + (y * q.z) - (z * q.y),
123             (y * q.w) + (w * q.y) + (z * q.x) - (x * q.z),
124             (z * q.w) + (w * q.z) + (x * q.y) - (y * q.x),
125             (w * q.w) - (x * q.x) - (y * q.y) - (z * q.z)
126         );
127     }
128 
129    /**
130     * Quaternion!(T) *= Quaternion!(T)
131     */
132     Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "*")
133     {
134         this = this * q;
135         return this;
136     }
137 
138    /**
139     * Quaternion!(T) * T
140     */
141     Quaternion!(T) opBinary(string op)(T k) if (op == "*")
142     {
143         return Quaternion!(T)(x * k, y * k, z * k, w * k);
144     }
145 
146    /**
147     * Quaternion!(T) *= T
148     */
149     Quaternion!(T) opOpAssign(string op)(T k) if (op == "*")
150     {
151         x *= k;
152         y *= k;
153         z *= k;
154         w *= k;
155         return this;
156     }
157 
158    /**
159     * T * Quaternion!(T)
160     */
161     Quaternion!(T) opBinaryRight(string op) (T k) if (op == "*")
162     {
163         return Quaternion!(T)(x * k, y * k, z * k, w * k);
164     }
165 
166    /**
167     * Quaternion!(T) / T
168     */
169     Quaternion!(T) opBinary(string op)(T k) if (op == "/")
170     {
171         T oneOverK = 1.0 / k;
172         return Quaternion!(T)
173         (
174             x * oneOverK,
175             y * oneOverK,
176             z * oneOverK,
177             w * oneOverK
178         );
179     }
180 
181    /**
182     * Quaternion!(T) /= T
183     */
184     Quaternion!(T) opOpAssign(string op)(T k) if (op == "/")
185     {
186         T oneOverK = 1.0 / k;
187         x *= oneOverK;
188         y *= oneOverK;
189         z *= oneOverK;
190         w *= oneOverK;
191         return this;
192     }
193 
194    /**
195     * Quaternion!(T) * Vector!(T,3)
196     */
197     Quaternion!(T) opBinary(string op)(Vector!(T,3) v) if (op == "*")
198     {
199         return Quaternion!(T)
200         (
201             (w * v.x) + (y * v.z) - (z * v.y),
202             (w * v.y) + (z * v.x) - (x * v.z),
203             (w * v.z) + (x * v.y) - (y * v.x),
204           - (x * v.x) - (y * v.y) - (z * v.z)
205         );
206     }
207 
208    /**
209     * Quaternion!(T) *= Vector!(T,3)
210     */
211     Quaternion!(T) opOpAssign(string op)(Vector!(T,3) v) if (op == "*")
212     {
213         this = this * v;
214         return this;
215     }
216 
217    /**
218     * Conjugate
219     * A quaternion with the opposite rotation
220     */
221     Quaternion!(T) conjugate()
222     {
223         return Quaternion!(T)(-x, -y, -z, w);
224     }
225 
226     alias conj = conjugate;
227 
228    /**
229     * Compute the W component of a unit length quaternion
230     */
231     void computeW()
232     {
233         T t = T(1.0) - (x * x) - (y * y) - (z * z);
234         if (t < 0.0)
235             w = 0.0;
236         else
237             w = -(t.sqrt);
238     }
239 
240    /**
241     * Rotate a point by quaternion
242     */
243     Vector!(T,3) rotate(Vector!(T,3) v)
244     {
245         Quaternion!(T) qf = this * v * this.conj;
246         return Vector!(T,3)(qf.x, qf.y, qf.z);
247     }
248 
249     Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) if (op == "*")
250     {
251         Quaternion!(T) qf = this * v * this.conj;
252         return Vector!(T,3)(qf.x, qf.y, qf.z);
253     }
254 
255     static if (isNumeric!(T))
256     {
257        /**
258         * Normalized version
259         */
260         Quaternion!(T) normalized()
261         {
262             Quaternion!(T) q = this;
263             q.normalize();
264             return q;
265         }
266 
267        /**
268         * Convert to 4x4 matrix
269         */
270         Matrix!(T,4) toMatrix4x4()
271         {
272             auto mat = Matrix!(T,4).identity;
273 
274             mat[0]  = 1.0 - 2.0 * (y * y + z * z);
275             mat[1]  = 2.0 * (x * y + z * w);
276             mat[2]  = 2.0 * (x * z - y * w);
277             mat[3]  = 0.0;
278 
279             mat[4]  = 2.0 * (x * y - z * w);
280             mat[5]  = 1.0 - 2.0 * (x * x + z * z);
281             mat[6]  = 2.0 * (z * y + x * w);
282             mat[7]  = 0.0;
283 
284             mat[8]  = 2.0 * (x * z + y * w);
285             mat[9]  = 2.0 * (y * z - x * w);
286             mat[10] = 1.0 - 2.0 * (x * x + y * y);
287             mat[11] = 0.0;
288 
289             mat[12] = 0.0;
290             mat[13] = 0.0;
291             mat[14] = 0.0;
292             mat[15] = 1.0;
293 
294             return mat;
295         }
296 
297        /**
298         * Convert to 3x3 matrix
299         */
300         Matrix!(T,3) toMatrix3x3()
301         {
302             auto mat = Matrix!(T,3).identity;
303 
304             mat[0] = 1.0 - 2.0 * (y * y + z * z);
305             mat[1] = 2.0 * (x * y + z * w);
306             mat[2] = 2.0 * (x * z - y * w);
307 
308             mat[3] = 2.0 * (x * y - z * w);
309             mat[4] = 1.0 - 2.0 * (x * x + z * z);
310             mat[5] = 2.0 * (z * y + x * w);
311 
312             mat[6] = 2.0 * (x * z + y * w);
313             mat[7] = 2.0 * (y * z - x * w);
314             mat[8] = 1.0 - 2.0 * (x * x + y * y);
315 
316             return mat;
317         }
318 
319        /**
320         * Setup the quaternion to perform a rotation,
321         * given the angular displacement in matrix form
322         */
323         static Quaternion!(T) fromMatrix(Matrix!(T,4) m)
324         {
325             Quaternion!(T) q;
326 
327             T trace = m.a11 + m.a22 + m.a33 + 1.0;
328 
329             if (trace > 0.0001)
330             {
331                 T s = 0.5 / sqrt(trace);
332                 q.w = 0.25 / s;
333                 q.x = (m.a23 - m.a32) * s;
334                 q.y = (m.a31 - m.a13) * s;
335                 q.z = (m.a12 - m.a21) * s;
336             }
337             else
338             {
339                 if ((m.a11 > m.a22) && (m.a11 > m.a33))
340                 {
341                     T s = 0.5 / sqrt(1.0 + m.a11 - m.a22 - m.a33);
342                     q.x = 0.25 / s;
343                     q.y = (m.a21 + m.a12) * s;
344                     q.z = (m.a31 + m.a13) * s;
345                     q.w = (m.a32 - m.a23) * s;
346                 }
347                 else if (m.a22 > m.a33)
348                 {
349                     T s = 0.5 / sqrt(1.0 + m.a22 - m.a11 - m.a33);
350                     q.x = (m.a21 + m.a12) * s;
351                     q.y = 0.25 / s;
352                     q.z = (m.a32 + m.a23) * s;
353                     q.w = (m.a31 - m.a13) * s;
354                 }
355                 else
356                 {
357                     T s = 0.5 / sqrt(1.0 + m.a33 - m.a11 - m.a22);
358                     q.x = (m.a31 + m.a13) * s;
359                     q.y = (m.a32 + m.a23) * s;
360                     q.z = 0.25 / s;
361                     q.w = (m.a21 - m.a12) * s;
362                 }
363             }
364 
365             return q;
366         }
367 
368        /**
369         * Setup the quaternion to perform a rotation,
370         * given the orientation in XYZ-Euler angles format (in radians)
371         */
372         static Quaternion!(T) fromEulerAngles(Vector!(T,3) e)
373         {
374             Quaternion!(T) q;
375 
376             T sr = sin(e.x * 0.5);
377             T cr = cos(e.x * 0.5);
378             T sp = sin(e.y * 0.5);
379             T cp = cos(e.y * 0.5);
380             T sy = sin(e.z * 0.5);
381             T cy = cos(e.z * 0.5);
382 
383             q.w =  (cy * cp * cr) + (sy * sp * sr);
384             q.x = -(sy * sp * cr) + (cy * cp * sr);
385             q.y =  (cy * sp * cr) + (sy * cp * sr);
386             q.z = -(cy * sp * sr) + (sy * cp * cr);
387 
388             return q;
389         }
390 
391        /**
392         * Setup the Euler angles, given a rotation Quaternion.
393         * Returned x,y,z are in radians
394         */
395         Vector!(T,3) toEulerAngles()
396         {
397             Vector!(T,3) e;
398 
399             e.y = asin(2.0 * ((x * z) + (w * y)));
400 
401             T cy = cos(e.y);
402             T oneOverCosY = 1.0 / cy;
403 
404             if (fabs(cy) > 0.001)
405             {
406                 e.x = atan2(2.0 * ((w * x) - (y * z)) * oneOverCosY,
407                            (1.0 - 2.0 *  (x*x + y*y)) * oneOverCosY);
408                 e.z = atan2(2.0 * ((w * z) - (x * y)) * oneOverCosY,
409                            (1.0 - 2.0 *  (y*y + z*z)) * oneOverCosY);
410             }
411             else
412             {
413                 e.x = 0.0;
414                 e.z = atan2(2.0 * ((x * y) + (w * z)),
415                             1.0 - 2.0 *  (x*x + z*z));
416             }
417 
418             return e;
419         }
420 
421        /**
422         * Return the rotation angle (in radians)
423         */
424         T rotationAngle()
425         {
426             return 2.0 * acos(w);
427         }
428 
429        /**
430         * Return the rotation axis
431         */
432         Vector!(T,3) rotationAxis()
433         {
434             T s = sqrt(1.0 - (w * w));
435 
436             if (s <= 0.0f)
437                 return Vector!(T,3)(x, y, z);
438             else
439                 return Vector!(T,3)(x / s, y / s, z / s);
440         }
441 
442        /**
443         * Quaternion as an angular velocity
444         */
445         Vector!(T,3) generator()
446         {
447             T s = sqrt(1.0 - (w * w));
448 
449             Vector!(T,3) axis;
450 
451             if (s <= 0.0)
452                 axis = Vector!(T,3)(x, y, z);
453             else
454                 axis = Vector!(T,3)(x * s, y * s, z * s);
455 
456             T angle = 2.0 * atan2(s, w);
457 
458             return axis * angle;
459         }
460     }
461 }
462 
463 /*
464  * Predefined quaternion type aliases
465  */
466 /// Alias for single precision Quaternion
467 alias Quaternionf = Quaternion!(float);
468 /// Alias for double precision Quaternion
469 alias Quaterniond = Quaternion!(double);
470 
471 ///
472 unittest
473 {
474     Quaternionf q1 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f);
475     Vector3f v1 = q1.rotate(Vector3f(1.0f, 0.0f, 0.0f));
476     assert(isAlmostZero(v1 - Vector3f(1.0f, 0.0f, 0.0f)));
477     
478     Quaternionf q2 = Quaternionf.identity;
479     assert(isConsiderZero(q2.x));
480     assert(isConsiderZero(q2.y));
481     assert(isConsiderZero(q2.z));
482     assert(isConsiderZero(q2.w - 1.0f));
483     
484     Quaternionf q3 = Quaternionf([1.0f, 0.0f, 0.0f, 1.0f]);
485     Quaternionf q4 = Quaternionf([0.0f, 1.0f, 0.0f, 1.0f]);
486     q4 = q3 * q4;
487     assert(q4 == Quaternionf(1, 1, 1, 1));
488     
489     Vector3f v2 = Vector3f(0, 0, 1);
490     Quaternionf q5 = Quaternionf(v2, 1.0f);
491     q5 *= q5;
492     assert(q5 == Quaternionf(0, 0, 2, 0));
493     
494     Quaternionf q6 = Quaternionf(Vector4f(1, 0, 0, 1));
495     Quaternionf q7 = q6 + q6 - Quaternionf(2, 0, 0, 2);
496     assert(q7 == Quaternionf(0, 0, 0, 0));
497     
498     Quaternionf q8 = Quaternionf(0.5f, 0.5f, 0.5f, 0.0f);
499     q8.computeW();
500     assert(q8.w == -0.5f);
501     
502     Quaternionf q9 = Quaternionf(0.5f, 0.0f, 0.0f, 0.0f);
503     q9 = q9.normalized;
504     assert(q9 == Quaternionf(1, 0, 0, 0));
505     
506     Quaternionf q10 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f);
507     Matrix4f m1 = q10.toMatrix4x4;
508     assert(m1 == matrixf(
509         1, 0, 0, 0,
510         0, 1, 0, 0,
511         0, 0, 1, 0,
512         0, 0, 0, 1)
513     );
514     
515     Matrix3f m2 = q10.toMatrix3x3;
516     assert(m2 == matrixf(
517         1, 0, 0,
518         0, 1, 0,
519         0, 0, 1)
520     );
521 }
522 
523 /**
524  * Setup a quaternion to rotate about world axis.
525  * Theta must be in radians
526  */
527 Quaternion!(T) rotationQuaternion(T)(uint rotaxis, T theta)
528 {
529     Quaternion!(T) res = Quaternion!(T).identity;
530     T thetaOver2 = theta * 0.5;
531 
532     switch (rotaxis)
533     {
534         case Axis.x:
535             res.w = cos(thetaOver2);
536             res.x = sin(thetaOver2);
537             res.y = 0.0;
538             res.z = 0.0;
539             break;
540 
541         case Axis.y:
542             res.w = cos(thetaOver2);
543             res.x = 0.0;
544             res.y = sin(thetaOver2);
545             res.z = 0.0;
546             break;
547 
548         case Axis.z:
549             res.w = cos(thetaOver2);
550             res.x = 0.0;
551             res.y = 0.0;
552             res.z = sin(thetaOver2);
553             break;
554 
555         default:
556         assert(0);
557     }
558 
559     return res;
560 }
561 
562 /**
563  * Setup a quaternion to rotate about specified axis.
564  * Theta must be in radians
565  */
566 Quaternion!(T) rotationQuaternion(T)(Vector!(T,3) rotaxis, T theta)
567 {
568     Quaternion!(T) res;
569 
570     T thetaOver2 = theta * 0.5;
571     T sinThetaOver2 = sin(thetaOver2);
572 
573     res.w = cos(thetaOver2);
574     res.x = rotaxis.x * sinThetaOver2;
575     res.y = rotaxis.y * sinThetaOver2;
576     res.z = rotaxis.z * sinThetaOver2;
577     return res;
578 }
579 
580 /**
581  * Setup a quaternion to represent rotation
582  * between two unit-length vectors
583  */
584 Quaternion!(T) rotationBetween(T)(Vector!(T,3) a, Vector!(T,3) b)
585 {
586     Quaternion!(T) q;
587 
588     float d = dot(a, b);
589     float angle = acos(d);
590 
591     Vector!(T,3) axis;
592     if (d < -0.9999)
593     {
594         Vector!(T,3) c;
595         if (a.y != 0.0 || a.z != 0.0)
596             c = Vector!(T,3)(1, 0, 0);
597         else
598             c = Vector!(T,3)(0, 1, 0);
599         axis = cross(a, c);
600         axis.normalize();
601         q = rotationQuaternion(axis, angle);
602     }
603     else if (d > 0.9999)
604     {
605         q = Quaternion!(T).identity;
606     }
607     else
608     {
609         axis = cross(a, b);
610         axis.normalize();
611         q = rotationQuaternion(axis, angle);
612     }
613 
614     return q;
615 }
616 
617 /**
618  * Quaternion logarithm
619  */
620 Quaternion!(T) log(T)(Quaternion!(T) q)
621 {
622     Quaternion!(T) res;
623     res.w = 0.0;
624 
625     if (fabs(q.w) < 1.0)
626     {
627         T theta = acos(q.w);
628         T sin_theta = sin(theta);
629 
630         if (fabs(sin_theta) > 0.00001)
631         {
632             T thetaOverSinTheta = theta / sin_theta;
633             res.x = q.x * thetaOverSinTheta;
634             res.y = q.y * thetaOverSinTheta;
635             res.z = q.z * thetaOverSinTheta;
636             return res;
637         }
638     }
639 
640     res.x = q.x;
641     res.y = q.y;
642     res.z = q.z;
643     return res;
644 }
645 
646 /**
647  * Quaternion exponential
648  */
649 Quaternion!(T) exp(T) (Quaternion!(T) q)
650 {
651     T theta = sqrt(dot(q, q));
652     T sin_theta = sin(theta);
653     Quaternion!(T) res;
654     res.w = cos(theta);
655 
656     if (fabs(sin_theta) > 0.00001)
657     {
658         T sinThetaOverTheta = sin_theta / theta;
659         res.x = q.x * sinThetaOverTheta;
660         res.y = q.y * sinThetaOverTheta;
661         res.z = q.z * sinThetaOverTheta;
662     }
663     else
664     {
665         res.x = q.x;
666         res.y = q.y;
667         res.z = q.z;
668     }
669 
670     return res;
671 }
672 
673 /**
674  * Quaternion exponentiation
675  */
676 Quaternion!(T) pow(T) (Quaternion!(T) q, T exponent)
677 {
678     if (fabs(q.w) > 0.9999)
679         return q;
680     T alpha = acos(q.w);
681     T newAlpha = alpha * exponent;
682     Vector!(T,3) n = Vector!(T,3)(q.x, q.y, q.z);
683     n *= sin(newAlpha) / sin(alpha);
684     return new Quaternion!(T)(n, cos(newAlpha));
685 }
686 
687 /**
688  * Spherical linear interpolation
689  */
690 Quaternion!(T) slerp(T)(
691     Quaternion!(T) q0,
692     Quaternion!(T) q1,
693     T t)
694 {
695     if (t <= 0.0) return q0;
696     if (t >= 1.0) return q1;
697 
698     T cosOmega = dot(q0, q1);
699     T q1w = q1.w;
700     T q1x = q1.x;
701     T q1y = q1.y;
702     T q1z = q1.z;
703 
704     if (cosOmega < 0.0)
705     {
706         q1w = -q1w;
707         q1x = -q1x;
708         q1y = -q1y;
709         q1z = -q1z;
710         cosOmega = -cosOmega;
711     }
712     assert (cosOmega < 1.1);
713 
714     T k0, k1;
715     if (cosOmega > 0.9999)
716     {
717         k0 = 1.0 - t;
718         k1 = t;
719     }
720     else
721     {
722         T sinOmega = sqrt(1.0 - (cosOmega * cosOmega));
723         T omega = atan2(sinOmega, cosOmega);
724         T oneOverSinOmega = 1.0 / sinOmega;
725         k0 = sin((1.0 - t) * omega) * oneOverSinOmega;
726         k1 = sin(t * omega) * oneOverSinOmega;
727     }
728 
729     Quaternion!(T) res = Quaternion!(T)
730     (
731         (k0 * q0.x) + (k1 * q1x),
732         (k0 * q0.y) + (k1 * q1y),
733         (k0 * q0.z) + (k1 * q1z),
734         (k0 * q0.w) + (k1 * q1w)
735     );
736     return res;
737 }
738 
739 /**
740  * Spherical cubic interpolation
741  */
742 Quaternion!(T) squad(T)(
743     Quaternion!(T) q0,
744     Quaternion!(T) qa,
745     Quaternion!(T) qb,
746     Quaternion!(T) q1,
747     T t)
748 {
749     T slerp_t = 2.0 * t * (1.0 - t);
750     Quaternion!(T) slerp_q0 = slerp(q0, q1, t);
751     Quaternion!(T) slerp_q1 = slerp(qa, qb, t);
752     return slerp(slerp_q0, slerp_q1, slerp_t);
753 }
754 
755 /**
756  * Compute intermediate quaternions for building spline segments
757  */
758 Quaternion!(T) intermediate(T)(
759     Quaternion!(T) qprev,
760     Quaternion!(T) qcurr,
761     Quaternion!(T) qnext,
762 ref Quaternion!(T) qa,
763 ref Quaternion!(T) qb)
764 in
765 {
766     assert (dot(qprev, qprev) <= 1.0001);
767     assert (dot(qcurr, qcurr) <= 1.0001);
768 }
769 do
770 {
771     Quaternion!(T) inv_prev = qprev.conj;
772     Quaternion!(T) inv_curr = qcurr.conj;
773 
774     Quaternion!(T) p0 = inv_prev * qcurr;
775     Quaternion!(T) p1 = inv_curr * qnext;
776 
777     Quaternion!(T) arg = (log(p0) - log(p1)) * 0.25;
778 
779     qa = qcurr * exp( arg);
780     qb = qcurr * exp(-arg);
781 }