View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry;
17  
18  import com.irurueta.algebra.ArrayUtils;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.WrongSizeException;
21  
22  import java.io.Serializable;
23  import java.util.Arrays;
24  
25  /**
26   * Contains a representation of a 3D rotation in a more precise and compact way
27   * than in matrix notation.
28   * This implementation of a quaternion contains values in the basis 1, i, j, k
29   * expressed as (a, b, c, d).
30   * a value is related only to the rotation angle, while b, c, d values are related
31   * both to the rotation axis and the rotation angle.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class Quaternion extends Rotation3D implements Serializable, Cloneable {
35  
36      /**
37       * Number of parameters contained in a quaternion.
38       */
39      public static final int N_PARAMS = 4;
40  
41      /**
42       * Number of euler angles.
43       */
44      public static final int N_ANGLES = 3;
45  
46      /**
47       * Threshold of axis norm to convert quaternions to axis and rotation angle.
48       */
49      public static final double AXIS_NORM_THRESHOLD = 1e-7;
50  
51      /**
52       * Large threshold of axis norm to convert quaternions to axis and rotation
53       * angle.
54       */
55      public static final double LARGE_AXIS_NORM_THRESHOLD = 1e-6;
56  
57      /**
58       * Trace threshold to convert rotation matrices into quaternions.
59       */
60      public static final double TRACE_THRESHOLD = 1e-8;
61  
62      /**
63       * Value corresponding to real numbers basis.
64       */
65      private double a;
66  
67      /**
68       * Value corresponding to basis i.
69       */
70      private double b;
71  
72      /**
73       * Value corresponding to basis j.
74       */
75      private double c;
76  
77      /**
78       * Value corresponding to basis k.
79       */
80      private double d;
81  
82      /**
83       * Indicates whether quaternion is normalized or not.
84       */
85      private boolean normalized;
86  
87      /**
88       * Default constructor.
89       * Creates a quaternion containing no rotation.
90       */
91      public Quaternion() {
92          a = 1.0;
93      }
94  
95      /**
96       * Constructor.
97       *
98       * @param a value corresponding to real numbers basis.
99       * @param b value corresponding to basis i.
100      * @param c value corresponding to basis j.
101      * @param d value corresponding to basis k.
102      */
103     public Quaternion(final double a, final double b, final double c, final double d) {
104         this.a = a;
105         this.b = b;
106         this.c = c;
107         this.d = d;
108     }
109 
110     /**
111      * Constructor.
112      *
113      * @param quaternion quaternion to be copied from.
114      */
115     public Quaternion(final Quaternion quaternion) {
116         fromQuaternion(quaternion);
117     }
118 
119     /**
120      * Constructor.
121      *
122      * @param values values to be stored in the quaternion expressed in the
123      *               basis (1, i, j, k)
124      * @throws IllegalArgumentException if provided array does not have length
125      *                                  4.
126      */
127     public Quaternion(final double[] values) {
128         setValues(values);
129     }
130 
131     /**
132      * Constructor.
133      *
134      * @param axis  a rotation axis.
135      * @param theta a rotation angle expressed in radians.
136      * @throws IllegalArgumentException if provided axis array does not have
137      *                                  length 3.
138      */
139     public Quaternion(final double[] axis, final double theta) {
140         setFromAxisAndRotation(axis, theta);
141     }
142 
143     /**
144      * Constructor from and axis 3D rotation.
145      *
146      * @param axisRotation an axis 3D rotation.
147      */
148     public Quaternion(final AxisRotation3D axisRotation) {
149         setFromAxisAndRotation(axisRotation);
150     }
151 
152     /**
153      * Constructor from euler angles.
154      *
155      * @param roll  roll angle expressed in radians.
156      * @param pitch pitch angle expressed in radians.
157      * @param yaw   yaw angle expressed in radians.
158      */
159     public Quaternion(final double roll, final double pitch, final double yaw) {
160         setFromEulerAngles(roll, pitch, yaw);
161     }
162 
163     /**
164      * Constructor from matrix rotation.
165      *
166      * @param matrixRotation matrix rotation.
167      */
168     public Quaternion(final MatrixRotation3D matrixRotation) {
169         setFromMatrixRotation(matrixRotation);
170     }
171 
172     /**
173      * Gets value corresponding to real numbers basis.
174      *
175      * @return value corresponding to real numbers basis.
176      */
177     public double getA() {
178         return a;
179     }
180 
181     /**
182      * Sets value corresponding to real numbers basis.
183      *
184      * @param a value corresponding to real numbers basis.
185      */
186     public void setA(final double a) {
187         this.a = a;
188         normalized = false;
189     }
190 
191     /**
192      * Gets value corresponding to basis i.
193      *
194      * @return value corresponding to basis i.
195      */
196     public double getB() {
197         return b;
198     }
199 
200     /**
201      * Sets value corresponding to basis i.
202      *
203      * @param b value corresponding to basis i.
204      */
205     public void setB(final double b) {
206         this.b = b;
207         normalized = false;
208     }
209 
210     /**
211      * Gets value corresponding to basis j.
212      *
213      * @return value corresponding to basis j.
214      */
215     public double getC() {
216         return c;
217     }
218 
219     /**
220      * Sets value corresponding to basis j.
221      *
222      * @param c value corresponding to basis j.
223      */
224     public void setC(final double c) {
225         this.c = c;
226         normalized = false;
227     }
228 
229     /**
230      * Gets value corresponding to basis k.
231      *
232      * @return value corresponding to basis k.
233      */
234     public double getD() {
235         return d;
236     }
237 
238     /**
239      * Sets value corresponding to basis k.
240      *
241      * @param d value corresponding to basis k.
242      */
243     public void setD(final double d) {
244         this.d = d;
245         normalized = false;
246     }
247 
248     /**
249      * Gets values that parameterize this quaternion.
250      *
251      * @return values of this quaternion.
252      */
253     public double[] getValues() {
254         final var result = new double[N_PARAMS];
255         values(result);
256         return result;
257     }
258 
259     /**
260      * Stores values that parameterize this quaternion into provided array.
261      *
262      * @param result array where quaternion parameters will be stored.
263      * @throws IllegalArgumentException if length of provided array is not 4.
264      */
265     public void values(final double[] result) {
266         if (result.length != N_PARAMS) {
267             throw new IllegalArgumentException("result length must be 4");
268         }
269 
270         result[0] = a;
271         result[1] = b;
272         result[2] = c;
273         result[3] = d;
274     }
275 
276     /**
277      * Sets values that parameterize this quaternion in basis (1, i, j ,k).
278      *
279      * @param values values that parameterize this quaternion in basis (1, i, j,
280      *               k).
281      * @throws IllegalArgumentException if provided array length is not 4.
282      */
283     public final void setValues(final double[] values) {
284         if (values.length != N_PARAMS) {
285             throw new IllegalArgumentException("values length must be 4");
286         }
287 
288         a = values[0];
289         b = values[1];
290         c = values[2];
291         d = values[3];
292         normalized = false;
293     }
294 
295     /**
296      * Copies values from provided quaternion into this instance.
297      *
298      * @param quaternion quaternion to copy from.
299      */
300     public final void fromQuaternion(final Quaternion quaternion) {
301         a = quaternion.a;
302         b = quaternion.b;
303         c = quaternion.c;
304         d = quaternion.d;
305         normalized = quaternion.normalized;
306     }
307 
308     /**
309      * Returns a new quaternion instance containing the same data as this
310      * instance.
311      *
312      * @return a copy of this quaternion instance.
313      * @throws CloneNotSupportedException if clone fails.
314      */
315     @Override
316     public Quaternion clone() throws CloneNotSupportedException {
317         final var result = (Quaternion) super.clone();
318         copyTo(result);
319         return result;
320     }
321 
322     /**
323      * Copies this instance data into provided quaternion instance.
324      *
325      * @param output destination instance where data is copied to.
326      */
327     public void copyTo(final Quaternion output) {
328         output.a = a;
329         output.b = b;
330         output.c = c;
331         output.d = d;
332         output.normalized = normalized;
333     }
334 
335     /**
336      * Sets quaternion parameters from axis and rotation values.
337      *
338      * @param axisX x coordinate of rotation axis.
339      * @param axisY y coordinate of rotation axis.
340      * @param axisZ z coordinate of rotation axis.
341      * @param theta rotation angle expressed in radians.
342      */
343     public final void setFromAxisAndRotation(
344             final double axisX, final double axisY, final double axisZ, final double theta) {
345         setFromAxisAndRotation(axisX, axisY, axisZ, theta, null, null);
346     }
347 
348     /**
349      * Sets quaternion parameters from axis and rotation values.
350      *
351      * @param axisX           x coordinate of rotation axis.
352      * @param axisY           y coordinate of rotation axis.
353      * @param axisZ           z coordinate of rotation axis.
354      * @param theta           rotation angle expressed in radians.
355      * @param jacobianOfTheta if provided, matrix where jacobian of rotation
356      *                        angle will be stored. Must be a 4x1 matrix.
357      * @param jacobianOfAxis  if provided, matrix where jacobian of rotation axis
358      *                        will be stored. Must be a 4x3 matrix.
359      * @throws IllegalArgumentException if any of the provided jacobian matrices
360      *                                  does not have proper size.
361      * @see <a href="https://github.com/joansola/slamtb">au2q.m at https://github.com/joansola/slamtb</a>
362      */
363     public void setFromAxisAndRotation(
364             final double axisX, final double axisY, final double axisZ, final double theta,
365             final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
366 
367         // validations
368         if (jacobianOfTheta != null && (jacobianOfTheta.getRows() != N_PARAMS || jacobianOfTheta.getColumns() != 1)) {
369             throw new IllegalArgumentException("jacobian of theta must be 4x1");
370         }
371 
372         if (jacobianOfAxis != null && (jacobianOfAxis.getRows() != N_PARAMS
373                 || jacobianOfAxis.getColumns() != N_ANGLES)) {
374             throw new IllegalArgumentException("jacobian of axis must be 4x3");
375         }
376 
377 
378         final var halfTheta = theta / 2.0;
379         final var cosine = Math.cos(halfTheta);
380         final var sine = Math.sin(halfTheta);
381 
382         a = cosine;
383 
384         b = axisX * sine;
385         this.c = axisY * sine;
386         d = axisZ * sine;
387         normalized = false;
388 
389         if (jacobianOfTheta != null) {
390             final var halfC = cosine / 2.0;
391             final var halfS = sine / 2.0;
392 
393             jacobianOfTheta.getBuffer()[0] = -halfS;
394             jacobianOfTheta.getBuffer()[1] = axisX * halfC;
395             jacobianOfTheta.getBuffer()[2] = axisY * halfC;
396             jacobianOfTheta.getBuffer()[3] = axisZ * halfC;
397         }
398 
399         if (jacobianOfAxis != null) {
400             jacobianOfAxis.initialize(0.0);
401             jacobianOfAxis.setElementAt(1, 0, sine);
402             jacobianOfAxis.setElementAt(2, 1, sine);
403             jacobianOfAxis.setElementAt(3, 2, sine);
404         }
405     }
406 
407     /**
408      * Sets quaternion parameters from axis and rotation values.
409      *
410      * @param axis  axis values.
411      * @param theta rotation angle expressed in radians.
412      * @throws IllegalArgumentException if provided axis array does not have
413      *                                  length 3.
414      */
415     public final void setFromAxisAndRotation(final double[] axis, final double theta) {
416         setFromAxisAndRotation(axis, theta, null, null);
417     }
418 
419     /**
420      * Sets quaternion parameters from axis and rotation values.
421      *
422      * @param axis            axis values.
423      * @param theta           rotation angle expressed in radians.
424      * @param jacobianOfTheta if provided, matrix where jacobian of rotation
425      *                        angle will be stored. Must be a 4x1 matrix.
426      * @param jacobianOfAxis  if provided, matrix where jacobian of rotation axis
427      *                        will be stored. Must be a 4x4 matrix.
428      * @throws IllegalArgumentException if provided axis array does not have
429      *                                  length 3, or if any of the provided jacobian matrices
430      *                                  does not have proper size.
431      */
432     public void setFromAxisAndRotation(
433             final double[] axis, final double theta, final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
434         if (axis.length != AxisRotation3D.AXIS_PARAMS) {
435             throw new IllegalArgumentException("axis length must be 3");
436         }
437 
438         setFromAxisAndRotation(axis[0], axis[1], axis[2], theta, jacobianOfTheta, jacobianOfAxis);
439     }
440 
441     /**
442      * Sets quaternion parameters from an axis 3D rotation.
443      *
444      * @param axisRotation an axis 3D rotation.
445      */
446     public final void setFromAxisAndRotation(final AxisRotation3D axisRotation) {
447         setFromAxisAndRotation(axisRotation, null, null);
448     }
449 
450     /**
451      * Sets quaternion parameters from an axis 3D rotation.
452      *
453      * @param axisRotation    an axis 3D rotation.
454      * @param jacobianOfTheta if provided, matrix where jacobian of rotation
455      *                        angle will be stored. Must be a 4x1 matrix.
456      * @param jacobianOfAxis  if provided, matrix where jacobian of rotation axis
457      *                        will be stored. Must be a 4x4 matrix.
458      * @throws IllegalArgumentException if any of the provided jacobian matrices
459      *                                  does not have proper size.
460      */
461     public void setFromAxisAndRotation(
462             final AxisRotation3D axisRotation, final Matrix jacobianOfTheta, final Matrix jacobianOfAxis) {
463 
464         final var theta = axisRotation.getRotationAngle();
465 
466         setFromAxisAndRotation(axisRotation.getAxisX(), axisRotation.getAxisY(), axisRotation.getAxisZ(), theta,
467                 jacobianOfTheta, jacobianOfAxis);
468     }
469 
470     /**
471      * Multiplies this quaternion with provided one and stores the result in
472      * this instance.
473      *
474      * @param q quaternion to multiply with.
475      */
476     public void multiply(final Quaternion q) {
477         multiply(q, this);
478     }
479 
480     /**
481      * Multiplies this quaternion with provided one and returns the result as a
482      * new quaternion instance.
483      *
484      * @param q quaternion to multiply with.
485      * @return obtained result.
486      */
487     public Quaternion multiplyAndReturnNew(final Quaternion q) {
488         final var result = new Quaternion(0.0, 0.0, 0.0, 0.0);
489         multiply(q, result);
490         return result;
491     }
492 
493     /**
494      * Multiplies this quaternion with provided one and stores the result into
495      * provided instance.
496      *
497      * @param q      quaternion to multiply with.
498      * @param result instance where result is stored.
499      */
500     public void multiply(final Quaternion q, final Quaternion result) {
501         product(this, q, result);
502     }
503 
504     /**
505      * Multiplies quaternion q1 with quaternion q2 and stores the result into
506      * provided instance.
507      *
508      * @param q1     1st product operator of quaternions.
509      * @param q2     2nd product operator of quaternions.
510      * @param result instance where result of product is stored.
511      */
512     public static void product(final Quaternion q1, final Quaternion q2, final Quaternion result) {
513         product(q1, q2, result, null, null);
514     }
515 
516     /**
517      * Multiplies quaternion q1 with quaternion q2 and stores the result into
518      * provided instance. This method also computes the Jacobians wrt of Q1 and
519      * Q2 if provided.
520      *
521      * @param q1         1st product operator of quaternions.
522      * @param q2         2nd product operator of quaternions.
523      * @param result     instance where result of product is stored.
524      * @param jacobianQ1 instance where jacobian of q1 is stored.
525      * @param jacobianQ2 instance where jacobian of q2 is stored.
526      * @throws IllegalArgumentException if any of the provided jacobian matrices
527      *                                  is not 4x4.
528      * @see <a href="https://github.com/joansola/slamtb">qProd.m at https://github.com/joansola/slamtb</a>
529      */
530     public static void product(
531             final Quaternion q1, final Quaternion q2, final Quaternion result, final Matrix jacobianQ1,
532             final Matrix jacobianQ2) {
533 
534         if (jacobianQ1 != null && (jacobianQ1.getRows() != Quaternion.N_PARAMS
535                 || jacobianQ1.getColumns() != Quaternion.N_PARAMS)) {
536             throw new IllegalArgumentException("jacobian of q1 must be 4x4");
537         }
538         if (jacobianQ2 != null && (jacobianQ2.getRows() != Quaternion.N_PARAMS
539                 || jacobianQ2.getColumns() != Quaternion.N_PARAMS)) {
540             throw new IllegalArgumentException("jacobian of q2 must be 4x4");
541         }
542 
543         final var q1A = q1.a;
544         final var q1B = q1.b;
545         final var q1C = q1.c;
546         final var q1D = q1.d;
547         final var q2A = q2.a;
548         final var q2B = q2.b;
549         final var q2C = q2.c;
550         final var q2D = q2.d;
551 
552         result.a = q1A * q2A - q1B * q2B - q1C * q2C - q1D * q2D;
553         result.b = q1A * q2B + q1B * q2A + q1C * q2D - q1D * q2C;
554         result.c = q1A * q2C - q1B * q2D + q1C * q2A + q1D * q2B;
555         result.d = q1A * q2D + q1B * q2C - q1C * q2B + q1D * q2A;
556         result.normalized = false;
557 
558         if (jacobianQ1 != null) {
559             jacobianQ1.setElementAt(0, 0, q2A);
560             jacobianQ1.setElementAt(1, 0, q2B);
561             jacobianQ1.setElementAt(2, 0, q2C);
562             jacobianQ1.setElementAt(3, 0, q2D);
563 
564             jacobianQ1.setElementAt(0, 1, -q2B);
565             jacobianQ1.setElementAt(1, 1, q2A);
566             jacobianQ1.setElementAt(2, 1, -q2D);
567             jacobianQ1.setElementAt(3, 1, q2C);
568 
569             jacobianQ1.setElementAt(0, 2, -q2C);
570             jacobianQ1.setElementAt(1, 2, q2D);
571             jacobianQ1.setElementAt(2, 2, q2A);
572             jacobianQ1.setElementAt(3, 2, -q2B);
573 
574             jacobianQ1.setElementAt(0, 3, -q2D);
575             jacobianQ1.setElementAt(1, 3, -q2C);
576             jacobianQ1.setElementAt(2, 3, q2B);
577             jacobianQ1.setElementAt(3, 3, q2A);
578         }
579 
580         if (jacobianQ2 != null) {
581             jacobianQ2.setElementAt(0, 0, q1A);
582             jacobianQ2.setElementAt(1, 0, q1B);
583             jacobianQ2.setElementAt(2, 0, q1C);
584             jacobianQ2.setElementAt(3, 0, q1D);
585 
586             jacobianQ2.setElementAt(0, 1, -q1B);
587             jacobianQ2.setElementAt(1, 1, q1A);
588             jacobianQ2.setElementAt(2, 1, q1D);
589             jacobianQ2.setElementAt(3, 1, -q1C);
590 
591             jacobianQ2.setElementAt(0, 2, -q1C);
592             jacobianQ2.setElementAt(1, 2, -q1D);
593             jacobianQ2.setElementAt(2, 2, q1A);
594             jacobianQ2.setElementAt(3, 2, q1B);
595 
596             jacobianQ2.setElementAt(0, 3, -q1D);
597             jacobianQ2.setElementAt(1, 3, q1C);
598             jacobianQ2.setElementAt(2, 3, -q1B);
599             jacobianQ2.setElementAt(3, 3, q1A);
600         }
601     }
602 
603     /**
604      * Sets quaternion from euler angles (roll, pitch and yaw).
605      *
606      * @param roll     roll angle expressed in radians. Rotation around x-axis.
607      * @param pitch    pitch angle expressed in radians. Rotation around y-axis.
608      * @param yaw      yaw angle expressed in radians. Rotation around z-axis.
609      * @param jacobian matrix where jacobian will be stored if provided.
610      * @throws IllegalArgumentException if provided jacobian matrix does not
611      *                                  have size 4x3
612      * @see <a href="https://github.com/joansola/slamtb">e2q.m at https://github.com/joansola/slamtb</a>
613      */
614     public void setFromEulerAngles(
615             final double roll, final double pitch, final double yaw, final Matrix jacobian) {
616 
617         if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_ANGLES)) {
618             throw new IllegalArgumentException("jacobian must be 4x3");
619         }
620 
621         // roll rotation on X axis
622         final var qx = new Quaternion(new double[]{1.0, 0.0, 0.0}, roll);
623         // pitch rotation on Y axis
624         final var qy = new Quaternion(new double[]{0.0, 1.0, 0.0}, pitch);
625         // yaw rotation on Z axis (qProd(qProd(qz, qy), qx)
626         final var qz = new Quaternion(new double[]{0.0, 0.0, 1.0}, yaw);
627 
628         product(qz, qy, this);
629         product(this, qx, this);
630         normalize();
631 
632         if (jacobian != null) {
633             final var halfRoll = roll / 2.0;
634             final var halfPitch = pitch / 2.0;
635             final var halfYaw = yaw / 2.0;
636 
637             final var sr = Math.sin(halfRoll);
638             final var sp = Math.sin(halfPitch);
639             final var sy = Math.sin(halfYaw);
640 
641             final var cr = Math.cos(halfRoll);
642             final var cp = Math.cos(halfPitch);
643             final var cy = Math.cos(halfYaw);
644 
645             jacobian.setElementAt(0, 0, 0.5 * (-cy * cp * sr + sy * sp * cr));
646             jacobian.setElementAt(1, 0, 0.5 * (cy * cp * cr + sy * sp * sr));
647             jacobian.setElementAt(2, 0, 0.5 * (-cy * sp * sr + sy * cp * cr));
648             jacobian.setElementAt(3, 0, 0.5 * (-sy * cp * sr - cy * sp * cr));
649 
650             jacobian.setElementAt(0, 1, 0.5 * (-cy * sp * cr + sy * cp * sr));
651             jacobian.setElementAt(1, 1, 0.5 * (-cy * sp * sr - sy * cp * cr));
652             jacobian.setElementAt(2, 1, 0.5 * (cy * cp * cr - sy * sp * sr));
653             jacobian.setElementAt(3, 1, 0.5 * (-cy * cp * sr - sy * sp * cr));
654 
655             jacobian.setElementAt(0, 2, 0.5 * (-sy * cp * cr + cy * sp * sr));
656             jacobian.setElementAt(1, 2, jacobian.getElementAt(3, 0));
657             jacobian.setElementAt(2, 2, 0.5 * (-sy * sp * cr + cy * cp * sr));
658             jacobian.setElementAt(3, 2, jacobian.getElementAt(1, 0));
659         }
660     }
661 
662     /**
663      * Sets quaternion from euler angles (roll, pitch and yaw).
664      *
665      * @param roll  roll angle expressed in radians. Rotation around x-axis.
666      * @param pitch pitch angle expressed in radians. Rotation around y-axis.
667      * @param yaw   yaw angle expressed in radians. Rotation around z-axis.
668      * @see <a href="https://github.com/joansola/slamtb">e2q.m at https://github.com/joansola/slamtb</a>
669      */
670     public final void setFromEulerAngles(final double roll, final double pitch, final double yaw) {
671         setFromEulerAngles(roll, pitch, yaw, null);
672     }
673 
674     /**
675      * Sets quaternion from euler angles.
676      *
677      * @param angles   euler angles expressed in radians in the following order:
678      *                 roll, pitch and yaw.
679      * @param jacobian matrix where jacobian will be stored if provided.
680      * @throws IllegalArgumentException if provided array does not have length
681      *                                  3.
682      */
683     public void setFromEulerAngles(final double[] angles, final Matrix jacobian) {
684         if (angles.length != N_ANGLES) {
685             throw new IllegalArgumentException("angles length must be 3");
686         }
687 
688         setFromEulerAngles(angles[0], angles[1], angles[2], jacobian);
689     }
690 
691     /**
692      * Sets quaternion from euler angles (roll, pitch and yaw).
693      *
694      * @param angles euler angles expressed in radians in the following order:
695      *               roll, pitch and yaw.
696      * @throws IllegalArgumentException if provided array does not have length
697      *                                  3.
698      */
699     public void setFromEulerAngles(final double[] angles) {
700         setFromEulerAngles(angles, null);
701     }
702 
703     /**
704      * Computes the rotation matrix body-to-world corresponding to the body
705      * orientation given by the Euler angles (roll, pitch, yaw).
706      *
707      * @param roll     roll angle expressed in radians. Rotation around x-axis.
708      * @param pitch    pitch angle expressed in radians. Rotation around y-axis.
709      * @param yaw      yaw angle expressed in radians. Rotation around z-axis.
710      * @param result   instance where computed rotation will be stored.
711      * @param jacobian jacobian of computed rotation (optional).
712      * @throws IllegalArgumentException if provided jacobian is not 9x3.
713      * @see <a href="https://github.com/joansola/slamtb">e2R.m at https://github.com/joansola/slamtb</a>
714      */
715     public static void eulerToMatrixRotation(
716             final double roll, final double pitch, final double yaw, final MatrixRotation3D result,
717             final Matrix jacobian) {
718 
719         if (jacobian != null && (jacobian.getRows() != 3 * N_ANGLES || jacobian.getColumns() != N_ANGLES)) {
720             throw new IllegalArgumentException("jacobian must be 9x3");
721         }
722 
723         result.setRollPitchYaw(roll, pitch, yaw);
724 
725         if (jacobian != null) {
726             final var sr = Math.sin(roll);
727             final var cr = Math.cos(roll);
728             final var sp = Math.sin(pitch);
729             final var cp = Math.cos(pitch);
730             final var sy = Math.sin(yaw);
731             final var cy = Math.cos(yaw);
732 
733             final var tmp1 = sr * sy + cr * sp * cy;
734             final var tmp2 = -cr * cy - sr * sp * sy;
735             jacobian.setElementAt(0, 0, 0.0);
736             jacobian.setElementAt(1, 0, 0.0);
737             jacobian.setElementAt(2, 0, 0.0);
738             jacobian.setElementAt(3, 0, tmp1);
739             jacobian.setElementAt(4, 0, -sr * cy + cr * sp * sy);
740             jacobian.setElementAt(5, 0, cr * cp);
741             jacobian.setElementAt(6, 0, cr * sy - sr * sp * cy);
742             jacobian.setElementAt(7, 0, tmp2);
743             jacobian.setElementAt(8, 0, -sr * cp);
744 
745             jacobian.setElementAt(0, 1, -sp * cy);
746             jacobian.setElementAt(1, 1, -sp * sy);
747             jacobian.setElementAt(2, 1, -cp);
748             jacobian.setElementAt(3, 1, sr * cp * cy);
749             jacobian.setElementAt(4, 1, sr * cp * sy);
750             jacobian.setElementAt(5, 1, -sr * sp);
751             jacobian.setElementAt(6, 1, cr * cp * cy);
752             jacobian.setElementAt(7, 1, cr * cp * sy);
753             jacobian.setElementAt(8, 1, -cr * sp);
754 
755             jacobian.setElementAt(0, 2, -cp * sy);
756             jacobian.setElementAt(1, 2, cp * cy);
757             jacobian.setElementAt(2, 2, 0.0);
758             jacobian.setElementAt(3, 2, tmp2);
759             jacobian.setElementAt(4, 2, -cr * sy + sr * sp * cy);
760             jacobian.setElementAt(5, 2, 0.0);
761             jacobian.setElementAt(6, 2, sr * cy - cr * sp * sy);
762             jacobian.setElementAt(7, 2, tmp1);
763             jacobian.setElementAt(8, 2, 0.0);
764         }
765     }
766 
767     /**
768      * Computes the rotation matrix body-to-world corresponding to the body
769      * orientation given by the Euler angles (roll, pitch, yaw).
770      *
771      * @param roll   roll angle expressed in radians. Rotation around x-axis.
772      * @param pitch  pitch angle expressed in radians. Rotation around y-axis.
773      * @param yaw    yaw angle expressed in radians. Rotation around z-axis.
774      * @param result instance where computed rotation will be stored.
775      * @see <a href="https://github.com/joansola/slamtb">e2R.m at https://github.com/joansola/slamtb</a>
776      */
777     public static void eulerToMatrixRotation(
778             final double roll, final double pitch, final double yaw, final MatrixRotation3D result) {
779         eulerToMatrixRotation(roll, pitch, yaw, result, null);
780     }
781 
782     /**
783      * Computes the rotation matrix body-to-world corresponding to the body
784      * orientation given by the Euler angles (roll, pitch, yaw).
785      *
786      * @param angles   array containing roll, pitch and yaw angles.
787      * @param result   instance where computed rotation will be stored.
788      * @param jacobian jacobian of computed rotation (optional).
789      * @throws IllegalArgumentException if provided angles length is not 3, or
790      *                                  if provided jacobian is not 9x3.
791      * @see <a href="https://github.com/joansola/slamtb">e2R.m at https://github.com/joansola/slamtb</a>
792      */
793     public static void eulerToMatrixRotation(
794             final double[] angles, final MatrixRotation3D result, final Matrix jacobian) {
795         if (angles.length != N_ANGLES) {
796             throw new IllegalArgumentException("angles must have length 3");
797         }
798 
799         eulerToMatrixRotation(angles[0], angles[1], angles[2], result, jacobian);
800     }
801 
802     /**
803      * Computes the rotation matrix body-to-world corresponding to the body
804      * orientation given by the Euler angles (roll, pitch, yaw).
805      *
806      * @param angles array containing roll, pitch and yaw angles.
807      * @param result instance where computed rotation will be stored.
808      * @throws IllegalArgumentException if provided angles length is not 3.
809      * @see <a href="https://github.com/joansola/slamtb">e2R.m at https://github.com/joansola/slamtb</a>
810      */
811     public static void eulerToMatrixRotation(final double[] angles, final MatrixRotation3D result) {
812         eulerToMatrixRotation(angles, result, null);
813     }
814 
815     /**
816      * Computes rotation angle and axis of this instance.
817      *
818      * @param axis          array where normalized rotation axis will be stored.
819      * @param jacobianAngle matrix where jacobian of angle will be stored, if
820      *                      provided. Must be 1x4.
821      * @param jacobianAxis  matrix where jacobian of axis will be stored, if
822      *                      provided. Must be 3x4.
823      * @return rotation angle expressed in radians.
824      * @throws IllegalArgumentException if length of axis or size of provided
825      *                                  jacobians is not correct.
826      * @see <a href="https://github.com/joansola/slamtb">q2au.m at https://github.com/joansola/slamtb</a>
827      */
828     public double toAxisAndRotationAngle(
829             final double[] axis, final Matrix jacobianAngle, final Matrix jacobianAxis) {
830         if (axis.length != AxisRotation3D.AXIS_PARAMS) {
831             throw new IllegalArgumentException("axis length must be 3");
832         }
833         if (jacobianAngle != null && (jacobianAngle.getRows() != 1 || jacobianAngle.getColumns() != N_PARAMS)) {
834             throw new IllegalArgumentException("jacobian of angle must be 1x4");
835         }
836         if (jacobianAxis != null && (jacobianAxis.getRows() != AxisRotation3D.AXIS_PARAMS
837                 || jacobianAxis.getColumns() != N_PARAMS)) {
838             throw new IllegalArgumentException("jacobian of axis must be 3x4");
839         }
840 
841         // non-normalized rotation axis
842         final var v = new double[]{b, c, d};
843 
844         // norm of rotation axis
845         final var n = com.irurueta.algebra.Utils.normF(v);
846 
847         // normalized rotation axis
848         if (n > 0.0) {
849             ArrayUtils.multiplyByScalar(v, 1.0 / n, axis);
850         } else {
851             axis[0] = axis[1] = 0.0;
852             axis[2] = 1.0;
853         }
854 
855         // scalar part
856         final var s = a;
857         final var aValue = 2.0 * Math.atan2(n, s);
858 
859         if (jacobianAngle != null) {
860             if (n > AXIS_NORM_THRESHOLD) {
861                 final var denom = n * n + s * s;
862                 final var aN = 2.0 * s / denom;
863                 final var aS = -2.0 * n / denom;
864                 final var aV = ArrayUtils.multiplyByScalarAndReturnNew(axis, aN);
865 
866                 jacobianAngle.setElementAtIndex(0, aS);
867                 jacobianAngle.setElementAtIndex(1, aV[0]);
868                 jacobianAngle.setElementAtIndex(2, aV[1]);
869                 jacobianAngle.setElementAtIndex(3, aV[2]);
870             } else {
871                 jacobianAngle.initialize(0.0);
872             }
873         }
874 
875         if (jacobianAxis != null) {
876             jacobianAxis.initialize(0.0);
877 
878             try {
879                 if (n > AXIS_NORM_THRESHOLD) {
880                     // uV = (eye(3)*n - v * axis') / n^2
881                     final var uV = Matrix.identity(AxisRotation3D.AXIS_PARAMS, AxisRotation3D.AXIS_PARAMS);
882                     uV.multiplyByScalar(n);
883                     uV.subtract(Matrix.newFromArray(v, true).multiplyAndReturnNew(
884                             Matrix.newFromArray(axis, false)));
885                     uV.multiplyByScalar(1.0 / (n * n));
886                     // uQ = [zeros(3, 1) uV]
887                     jacobianAxis.setSubmatrix(0, 1, 2, 3, uV);
888                 } else {
889                     // 2*eye(3)
890                     final var m = Matrix.identity(AxisRotation3D.AXIS_PARAMS, AxisRotation3D.AXIS_PARAMS);
891                     m.multiplyByScalar(2.0);
892                     // uQ = [zeros(3,1) 2*eye(3)]
893                     jacobianAxis.setSubmatrix(0, 1, 2, 3, m);
894                 }
895             } catch (final WrongSizeException ignore) {
896                 // never thrown
897             }
898         }
899 
900         return aValue;
901     }
902 
903     /**
904      * Computes rotation angle and axis.
905      *
906      * @param axis normalized rotation axis.
907      * @return rotation angle expressed in radians.
908      * @throws IllegalArgumentException if length of axis is not 3.
909      */
910     public double toAxisAndRotationAngle(final double[] axis) {
911         return toAxisAndRotationAngle(axis, null, null);
912     }
913 
914     /**
915      * Converts this quaternion into an axis 3D rotation and stores the result
916      * into provided rotation instance.
917      *
918      * @param result rotation instance where result will be stored.
919      */
920     @Override
921     public void toAxisRotation(final AxisRotation3D result) {
922         final var axis = new double[AxisRotation3D.AXIS_PARAMS];
923         final var theta = toAxisAndRotationAngle(axis, null, null);
924         result.setAxisAndRotation(axis, theta);
925     }
926 
927     /**
928      * Converts this quaternion into an axis 3D rotation.
929      *
930      * @return a new axis 3D rotation equivalent to this quaternion.
931      */
932     @Override
933     public AxisRotation3D toAxisRotation() {
934         final var result = new AxisRotation3D();
935         toAxisRotation(result);
936         return result;
937     }
938 
939     /**
940      * Computes rotation vector, which is equivalent to the rotation axis but
941      * having a norm equal to the rotation angle.
942      *
943      * @param result   array where rotation vector is stored.
944      * @param jacobian matrix where jacobian of vector will be stored, if
945      *                 provided.
946      * @throws IllegalArgumentException if length of result is not 3 or size of
947      *                                  provided jacobian is not 3x4.
948      * @see <a href="https://github.com/joansola/slamtb">q2v.m at https://github.com/joansola/slamtb</a>
949      */
950     public void toRotationVector(final double[] result, final Matrix jacobian) {
951         if (result.length != AxisRotation3D.AXIS_PARAMS) {
952             throw new IllegalArgumentException("result length must be 3");
953         }
954         if (jacobian != null && (jacobian.getRows() != AxisRotation3D.AXIS_PARAMS
955                 || jacobian.getColumns() != N_PARAMS)) {
956             throw new IllegalArgumentException("jacobian must be 3x4");
957         }
958 
959         if (jacobian == null) {
960             final var theta = toAxisAndRotationAngle(result, null, null);
961             ArrayUtils.multiplyByScalar(result, theta, result);
962         } else {
963             try {
964                 final var jacobianAngle = new Matrix(1, N_PARAMS);
965                 final var jacobianAxis = new Matrix(AxisRotation3D.AXIS_PARAMS, N_PARAMS);
966                 final var axis = new double[AxisRotation3D.AXIS_PARAMS];
967                 final var theta = toAxisAndRotationAngle(axis, jacobianAngle, jacobianAxis);
968                 ArrayUtils.multiplyByScalar(axis, theta, result);
969 
970                 final var vA = Matrix.newFromArray(axis, true);
971                 final var vU = Matrix.diagonal(new double[]{theta, theta, theta});
972 
973                 if (theta > AXIS_NORM_THRESHOLD) {
974                     // vA * jacobianAngle + vU * jacobianAxis //3x1 * 1x4 + 3x3 * 3x4
975 
976                     // vA * jacobianAngle
977                     vA.multiply(jacobianAngle);
978                     // vU * jacobianAxis
979                     vU.multiply(jacobianAxis);
980                     // vA * jacobianAngle + vU * jacobianAxis
981                     vA.add(vU);
982 
983                     jacobian.copyFrom(vA);
984                 } else {
985                     // 2*eye(3)
986                     final var m = Matrix.identity(AxisRotation3D.AXIS_PARAMS,
987                             AxisRotation3D.AXIS_PARAMS);
988                     m.multiplyByScalar(2.0);
989                     // uQ = [zeros(3,1) 2*eye(3)]
990                     jacobian.setSubmatrix(0, 1, 2, 3, m);
991                 }
992             } catch (final WrongSizeException ignore) {
993                 // never thrown
994             }
995         }
996     }
997 
998     /**
999      * Computes rotation vector, which is equivalent to the rotation axis but
1000      * having a norm equal to the rotation angle.
1001      *
1002      * @param result array where rotation vector is stored.
1003      * @throws IllegalArgumentException if length of result is not 3.
1004      */
1005     public void toRotationVector(final double[] result) {
1006         toRotationVector(result, null);
1007     }
1008 
1009     /**
1010      * Computes the euler angles (roll, pitch, yaw) equivalent to this
1011      * quaternion rotation and stores the result into provided array.
1012      * If provided, this method also computes the jacobian matrix.
1013      *
1014      * @param angles   euler angles (roll, pitch, yaw).
1015      * @param jacobian matrix where jacobian is stored, if provided.
1016      * @throws IllegalArgumentException if provided angles array length is not 3
1017      *                                  or if provided jacobian matrix is not 3x4.
1018      * @see <a href="https://github.com/joansola/slamtb">q2e.m at https://github.com/joansola/slamtb</a>
1019      */
1020     public void toEulerAngles(final double[] angles, final Matrix jacobian) {
1021         if (angles.length != N_ANGLES) {
1022             throw new IllegalArgumentException("angles length must be 3");
1023         }
1024         if (jacobian != null && (jacobian.getRows() != N_ANGLES || jacobian.getColumns() != N_PARAMS)) {
1025             throw new IllegalArgumentException("jacobian must be 3x4");
1026         }
1027 
1028         final var y1 = 2.0 * c * d + 2.0 * a * b;
1029         final var x1 = a * a - b * b - c * c + d * d;
1030         final var z2 = -2.0 * b * d + 2.0 * a * c;
1031         final var y3 = 2.0 * b * c + 2.0 * a * d;
1032         final var x3 = a * a + b * b - c * c - d * d;
1033 
1034         // roll
1035         angles[0] = Math.atan2(y1, x1);
1036 
1037         // pitch
1038         angles[1] = Math.asin(z2);
1039 
1040         // yaw
1041         angles[2] = Math.atan2(y3, x3);
1042 
1043         if (jacobian != null) {
1044             final var dx1dq = new double[]{2 * a, -2 * b, -2 * c, 2 * d};
1045             final var dy1dq = new double[]{2 * b, 2 * a, 2 * d, 2 * c};
1046             final var dz2dq = new double[]{2 * c, -2 * d, 2 * a, -2 * b};
1047             final var dx3dq = new double[]{2 * a, 2 * b, -2 * c, -2 * d};
1048             final var dy3dq = new double[]{2 * d, 2 * c, 2 * b, 2 * a};
1049 
1050             final var de1dx1 = -y1 / (x1 * x1 + y1 * y1);
1051             final var de1dy1 = x1 / (x1 * x1 + y1 * y1);
1052             final var de2dz2 = 1 / Math.sqrt(1 - z2 * z2);
1053             final var de3dx3 = -y3 / (x3 * x3 + y3 * y3);
1054             final var de3dy3 = x3 / (x3 * x3 + y3 * y3);
1055 
1056             // de1dq = de1dx1 * dx1dq + de1dy * dy1dq
1057             ArrayUtils.multiplyByScalar(dx1dq, de1dx1, dx1dq);
1058             ArrayUtils.multiplyByScalar(dy1dq, de1dy1, dy1dq);
1059             final var de1dq = ArrayUtils.sumAndReturnNew(dx1dq, dy1dq);
1060 
1061             // de2dq = de2dz2 * dz2dq
1062             final var de2dq = ArrayUtils.multiplyByScalarAndReturnNew(dz2dq, de2dz2);
1063 
1064             // de3dq = de3dx3 * dx3dq + de3dy3 * dy3dq
1065             ArrayUtils.multiplyByScalar(dx3dq, de3dx3, dx3dq);
1066             ArrayUtils.multiplyByScalar(dy3dq, de3dy3, dy3dq);
1067             final var de3dq = ArrayUtils.sumAndReturnNew(dx3dq, dy3dq);
1068 
1069             jacobian.setSubmatrix(0, 0, 0, N_PARAMS - 1, de1dq);
1070             jacobian.setSubmatrix(1, 0, 1, N_PARAMS - 1, de2dq);
1071             jacobian.setSubmatrix(2, 0, 2, N_PARAMS - 1, de3dq);
1072         }
1073     }
1074 
1075     /**
1076      * Computes the euler angles (roll, pitch, yaw) equivalent to this
1077      * quaternion rotation and stores the result into provided array.
1078      *
1079      * @param angles euler angles (roll, pitch, yaw).
1080      * @throws IllegalArgumentException if provided angles array length is not
1081      *                                  3.
1082      */
1083     public void toEulerAngles(final double[] angles) {
1084         toEulerAngles(angles, null);
1085     }
1086 
1087     /**
1088      * Computes the euler angles (roll, pitch, yaw) resulting in an equivalent
1089      * rotation to this quaternion.
1090      *
1091      * @return euler angles (roll, pitch, yaw)
1092      */
1093     public double[] toEulerAngles() {
1094         final var result = new double[N_ANGLES];
1095         toEulerAngles(result, null);
1096         return result;
1097     }
1098 
1099     /**
1100      * Converts this quaternion into a quaternion matrix so that the quaternion
1101      * product q1 x q2 is equivalent to the matrix product:
1102      * q1.toQuaternionMatrix().multiplyAndReturnNew(q2.toQuaternionMatrix())
1103      *
1104      * @param result matrix where result will be stored.
1105      * @throws IllegalArgumentException if provided matrix is not 4x4.
1106      * @see <a href="https://github.com/joansola/slamtb">q2Q.m at https://github.com/joansola/slamtb</a>
1107      */
1108     public void quaternionMatrix(final Matrix result) {
1109         if (result.getRows() != N_PARAMS || result.getColumns() != N_PARAMS) {
1110             throw new IllegalArgumentException("matrix must be 4x4");
1111         }
1112 
1113         result.setElementAt(0, 0, a);
1114         result.setElementAt(1, 0, b);
1115         result.setElementAt(2, 0, c);
1116         result.setElementAt(3, 0, d);
1117 
1118         result.setElementAt(0, 1, -b);
1119         result.setElementAt(1, 1, a);
1120         result.setElementAt(2, 1, d);
1121         result.setElementAt(3, 1, -c);
1122 
1123         result.setElementAt(0, 2, -c);
1124         result.setElementAt(1, 2, -d);
1125         result.setElementAt(2, 2, a);
1126         result.setElementAt(3, 2, b);
1127 
1128         result.setElementAt(0, 3, -d);
1129         result.setElementAt(1, 3, c);
1130         result.setElementAt(2, 3, -b);
1131         result.setElementAt(3, 3, a);
1132     }
1133 
1134     /**
1135      * Converts this quaternion into a quaternion matrix so that quaternion
1136      * product q1 x q2 is equivalent to the matrix product:
1137      * q1.toQuaternionMatrix().multiplyAndReturnNew(q2.toQuaternionMatrix())
1138      *
1139      * @return the quaternion matrix.
1140      * @see <a href="https://github.com/joansola/slamtb">q2Q.m at https://github.com/joansola/slamtb</a>
1141      */
1142     public Matrix toQuaternionMatrix() {
1143         Matrix result = null;
1144         try {
1145             result = new Matrix(N_PARAMS, N_PARAMS);
1146             quaternionMatrix(result);
1147         } catch (final WrongSizeException ignore) {
1148             // never thrown
1149         }
1150         return result;
1151     }
1152 
1153     /**
1154      * Computes the conjugate of this quaternion and stores the result into
1155      * provided instance.
1156      *
1157      * @param result   instance where result is stored.
1158      * @param jacobian matrix where jacobian is stored.
1159      * @throws IllegalArgumentException if provided jacobian matrix is not 4x4.
1160      * @see <a href="https://github.com/joansola/slamtb">q2qc.m at https://github.com/joansola/slamtb</a>
1161      */
1162     public void conjugate(final Quaternion result, final Matrix jacobian) {
1163         if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_PARAMS)) {
1164             throw new IllegalArgumentException("jacobian must be 4x4");
1165         }
1166 
1167         result.a = a;
1168         result.b = -b;
1169         result.c = -c;
1170         result.d = -d;
1171         result.normalized = normalized;
1172 
1173         if (jacobian != null) {
1174             jacobian.initialize(0.0);
1175             jacobian.setElementAt(0, 0, 1.0);
1176             for (int i = 1; i < N_PARAMS; i++) {
1177                 jacobian.setElementAt(i, i, -1.0);
1178             }
1179         }
1180     }
1181 
1182     /**
1183      * Computes the conjugate of this quaternion and stores the result into
1184      * provided instance.
1185      *
1186      * @param result instance where result is stored.
1187      * @see <a href="https://github.com/joansola/slamtb">q2qc.m at https://github.com/joansola/slamtb</a>
1188      */
1189     public void conjugate(final Quaternion result) {
1190         conjugate(result, null);
1191     }
1192 
1193     /**
1194      * Computes the conjugate of this quaternion.
1195      *
1196      * @return conjugate of this quaternion.
1197      * @see <a href="https://github.com/joansola/slamtb">q2qc.m at https://github.com/joansola/slamtb</a>
1198      */
1199     public Quaternion conjugateAndReturnNew() {
1200         final var q = new Quaternion();
1201         conjugate(q);
1202         return q;
1203     }
1204 
1205     /**
1206      * Converts this quaternion into a quaternion matrix so that the quaternion
1207      * product q1 x q2 is equivalent to the matrix product:
1208      * q2.toQuaternionMatrixN().multiplyAndReturnNew(q1.toQuaternionMatrixN()).
1209      * Notice that matrix order in the product is the opposite as the order used
1210      * when multiplying matrices obtained by method #toQuaternionMatrix().
1211      *
1212      * @param result matrix where result will be stored.
1213      * @throws IllegalArgumentException if provided matrix is not 4x4.
1214      * @see <a href="https://github.com/joansola/slamtb">q2Qn.m at https://github.com/joansola/slamtb</a>
1215      */
1216     public void quaternionMatrixN(final Matrix result) {
1217         if (result.getRows() != N_PARAMS || result.getColumns() != N_PARAMS) {
1218             throw new IllegalArgumentException("matrix must be 4x4");
1219         }
1220 
1221         result.setElementAt(0, 0, a);
1222         result.setElementAt(1, 0, b);
1223         result.setElementAt(2, 0, c);
1224         result.setElementAt(3, 0, d);
1225 
1226         result.setElementAt(0, 1, -b);
1227         result.setElementAt(1, 1, a);
1228         result.setElementAt(2, 1, -d);
1229         result.setElementAt(3, 1, c);
1230 
1231         result.setElementAt(0, 2, -c);
1232         result.setElementAt(1, 2, d);
1233         result.setElementAt(2, 2, a);
1234         result.setElementAt(3, 2, -b);
1235 
1236         result.setElementAt(0, 3, -d);
1237         result.setElementAt(1, 3, -c);
1238         result.setElementAt(2, 3, b);
1239         result.setElementAt(3, 3, a);
1240     }
1241 
1242     /**
1243      * Converts this quaternion into a quaternion matrix so that quaternion
1244      * product q1 x q2 is equivalent to the matrix product:
1245      * q2.toQuaternionMatrixN().multiplyAndReturnNew(q1.toQuaternionMatrixN()).
1246      * Notice that matrix order in the product is the opposite as the order used
1247      * when multiplying matrices obtained by method #toQuaternionMatrix().
1248      *
1249      * @return the quaternion matrix.
1250      * @see <a href="https://github.com/joansola/slamtb">q2Qn.m at https://github.com/joansola/slamtb</a>
1251      */
1252     public Matrix toQuaternionMatrixN() {
1253         Matrix result = null;
1254         try {
1255             result = new Matrix(N_PARAMS, N_PARAMS);
1256             quaternionMatrixN(result);
1257         } catch (final WrongSizeException ignore) {
1258             // never thrown
1259         }
1260         return result;
1261     }
1262 
1263     /**
1264      * Computes the matrix representing this quaternion rotation.
1265      *
1266      * @param result   matrix where rotation data will be stored.
1267      * @param jacobian jacobian wrt of this quaternion.
1268      * @throws IllegalArgumentException if provided result matrix is not 3x3 o
1269      *                                  jacobian matrix is not 9x4.
1270      * @see <a href="https://github.com/joansola/slamtb">q2R.m at https://github.com/joansola/slamtb</a>
1271      */
1272     public void toMatrixRotation(final Matrix result, final Matrix jacobian) {
1273         if (result.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
1274                 || result.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS) {
1275             throw new IllegalArgumentException("result matrix is not 3x3");
1276         }
1277         if (jacobian != null && (jacobian.getRows() != 9 || jacobian.getColumns() != 4)) {
1278             throw new IllegalArgumentException("jacobian matrix is not 9x4");
1279         }
1280 
1281         final var aa = a * a;
1282         final var ab = 2.0 * a * b;
1283         final var ac = 2.0 * a * c;
1284         final var ad = 2.0 * a * d;
1285         final var bb = b * b;
1286         final var bc = 2.0 * b * c;
1287         final var bd = 2.0 * b * d;
1288         final var cc = c * c;
1289         final var cd = 2.0 * c * d;
1290         final var dd = d * d;
1291 
1292         result.setElementAt(0, 0, aa + bb - cc - dd);
1293         result.setElementAt(1, 0, bc + ad);
1294         result.setElementAt(2, 0, bd - ac);
1295 
1296         result.setElementAt(0, 1, bc - ad);
1297         result.setElementAt(1, 1, aa - bb + cc - dd);
1298         result.setElementAt(2, 1, cd + ab);
1299 
1300         result.setElementAt(0, 2, bd + ac);
1301         result.setElementAt(1, 2, cd - ab);
1302         result.setElementAt(2, 2, aa - bb - cc + dd);
1303 
1304         if (jacobian != null) {
1305             final var a2 = 2.0 * a;
1306             final var b2 = 2.0 * b;
1307             final var c2 = 2.0 * c;
1308             final var d2 = 2.0 * d;
1309 
1310             jacobian.setElementAt(0, 0, a2);
1311             jacobian.setElementAt(1, 0, d2);
1312             jacobian.setElementAt(2, 0, -c2);
1313             jacobian.setElementAt(3, 0, -d2);
1314             jacobian.setElementAt(4, 0, a2);
1315             jacobian.setElementAt(5, 0, b2);
1316             jacobian.setElementAt(6, 0, c2);
1317             jacobian.setElementAt(7, 0, -b2);
1318             jacobian.setElementAt(8, 0, a2);
1319 
1320             jacobian.setElementAt(0, 1, b2);
1321             jacobian.setElementAt(1, 1, c2);
1322             jacobian.setElementAt(2, 1, d2);
1323             jacobian.setElementAt(3, 1, c2);
1324             jacobian.setElementAt(4, 1, -b2);
1325             jacobian.setElementAt(5, 1, a2);
1326             jacobian.setElementAt(6, 1, d2);
1327             jacobian.setElementAt(7, 1, -a2);
1328             jacobian.setElementAt(8, 1, -b2);
1329 
1330             jacobian.setElementAt(0, 2, -c2);
1331             jacobian.setElementAt(1, 2, b2);
1332             jacobian.setElementAt(2, 2, -a2);
1333             jacobian.setElementAt(3, 2, b2);
1334             jacobian.setElementAt(4, 2, c2);
1335             jacobian.setElementAt(5, 2, d2);
1336             jacobian.setElementAt(6, 2, a2);
1337             jacobian.setElementAt(7, 2, d2);
1338             jacobian.setElementAt(8, 2, -c2);
1339 
1340             jacobian.setElementAt(0, 3, -d2);
1341             jacobian.setElementAt(1, 3, a2);
1342             jacobian.setElementAt(2, 3, b2);
1343             jacobian.setElementAt(3, 3, -a2);
1344             jacobian.setElementAt(4, 3, -d2);
1345             jacobian.setElementAt(5, 3, c2);
1346             jacobian.setElementAt(6, 3, b2);
1347             jacobian.setElementAt(7, 3, c2);
1348             jacobian.setElementAt(8, 3, d2);
1349         }
1350     }
1351 
1352     /**
1353      * Computes the matrix representing this quaternion rotation.
1354      *
1355      * @param result matrix where rotation data will be stored.
1356      * @throws IllegalArgumentException if provided result matrix is not 3x3.
1357      * @see <a href="https://github.com/joansola/slamtb">q2R.m at https://github.com/joansola/slamtb</a>
1358      */
1359     public void toMatrixRotation(final Matrix result) {
1360         toMatrixRotation(result, null);
1361     }
1362 
1363     /**
1364      * Converts this quaternion into a 3D matrix rotation.
1365      *
1366      * @param result matrix rotation instance where result will be stored.
1367      * @see <a href="https://github.com/joansola/slamtb">q2R.m at https://github.com/joansola/slamtb</a>
1368      */
1369     @Override
1370     public void toMatrixRotation(final MatrixRotation3D result) {
1371         toMatrixRotation(result.internalMatrix);
1372     }
1373 
1374     /**
1375      * Converts this quaternion into a 3D matrix rotation.
1376      *
1377      * @return a 3D matrix rotation.
1378      * @see <a href="https://github.com/joansola/slamtb">q2R.m at https://github.com/joansola/slamtb</a>
1379      */
1380     @Override
1381     public MatrixRotation3D toMatrixRotation() {
1382         final var rotation = new MatrixRotation3D();
1383         toMatrixRotation(rotation);
1384         return rotation;
1385     }
1386 
1387     /**
1388      * Rotates a 3D point using the origin of coordinates as the axis of
1389      * rotation.
1390      * Point will be rotated by the amount of rotation contained in provided
1391      * quaternion.
1392      *
1393      * @param q                  a quaternion.
1394      * @param inputPoint         input point to be rotated.
1395      * @param resultPoint        rotated point.
1396      * @param jacobianPoint      jacobian wrt of point.
1397      * @param jacobianQuaternion jacobian wrt of quaternion.
1398      * @throws IllegalArgumentException if jacobian of point is not 3x3 or
1399      *                                  jacobian of quaternion is not 3x4.
1400      * @see <a href="https://github.com/joansola/slamtb">qRot.m at https://github.com/joansola/slamtb</a>
1401      */
1402     public static void rotate(final Quaternion q, final Point3D inputPoint, final Point3D resultPoint,
1403                               final Matrix jacobianPoint, final Matrix jacobianQuaternion) {
1404         if (jacobianPoint != null && (jacobianPoint.getRows() != N_ANGLES || jacobianPoint.getColumns() != N_ANGLES)) {
1405             throw new IllegalArgumentException("jacobian of point must be 3x3");
1406         }
1407         if (jacobianQuaternion != null && (jacobianQuaternion.getRows() != N_ANGLES
1408                 || jacobianQuaternion.getColumns() != N_PARAMS)) {
1409             throw new IllegalArgumentException("jacobian of quaternion must be 3x4");
1410         }
1411 
1412         final var v0 = new Quaternion(0.0, inputPoint.getInhomX(), inputPoint.getInhomY(), inputPoint.getInhomZ());
1413 
1414         final var tmp = q.multiplyAndReturnNew(v0).multiplyAndReturnNew(q.conjugateAndReturnNew());
1415 
1416         resultPoint.setInhomogeneousCoordinates(tmp.getB(), tmp.getC(), tmp.getD());
1417 
1418         if (jacobianPoint != null) {
1419             q.toMatrixRotation(jacobianPoint);
1420         }
1421 
1422         if (jacobianQuaternion != null) {
1423             final var a = q.a;
1424             final var b = q.b;
1425             final var c = q.c;
1426             final var d = q.d;
1427 
1428             final var x = inputPoint.getInhomX();
1429             final var y = inputPoint.getInhomY();
1430             final var z = inputPoint.getInhomZ();
1431 
1432             final var axdycz = 2.0 * (a * x - d * y + c * z);
1433             final var bxcydz = 2.0 * (b * x + c * y + d * z);
1434             final var cxbyaz = 2.0 * (c * x - b * y - a * z);
1435             final var dxaybz = 2.0 * (d * x + a * y - b * z);
1436 
1437             jacobianQuaternion.setElementAt(0, 0, axdycz);
1438             jacobianQuaternion.setElementAt(1, 0, dxaybz);
1439             jacobianQuaternion.setElementAt(2, 0, -cxbyaz);
1440 
1441             jacobianQuaternion.setElementAt(0, 1, bxcydz);
1442             jacobianQuaternion.setElementAt(1, 1, cxbyaz);
1443             jacobianQuaternion.setElementAt(2, 1, dxaybz);
1444 
1445             jacobianQuaternion.setElementAt(0, 2, -cxbyaz);
1446             jacobianQuaternion.setElementAt(1, 2, bxcydz);
1447             jacobianQuaternion.setElementAt(2, 2, -axdycz);
1448 
1449             jacobianQuaternion.setElementAt(0, 3, -dxaybz);
1450             jacobianQuaternion.setElementAt(1, 3, axdycz);
1451             jacobianQuaternion.setElementAt(2, 3, bxcydz);
1452         }
1453     }
1454 
1455     /**
1456      * Rotates a 3D point using the origin of coordinates as the axis of
1457      * rotation.
1458      * Point will be rotated by the amount of rotation contained in this
1459      * quaternion instance.
1460      *
1461      * @param inputPoint         input point to be rotated.
1462      * @param resultPoint        rotated point.
1463      * @param jacobianPoint      jacobian wrt of point.
1464      * @param jacobianQuaternion jacobian wrt of quaternion.
1465      * @throws IllegalArgumentException if jacobian of point is not 3x3 or
1466      *                                  jacobian of quaternion is no 3x4.
1467      * @see <a href="https://github.com/joansola/slamtb">qRot.m at https://github.com/joansola/slamtb</a>
1468      */
1469     public void rotate(final Point3D inputPoint, final Point3D resultPoint, final Matrix jacobianPoint,
1470                        final Matrix jacobianQuaternion) {
1471 
1472         rotate(this, inputPoint, resultPoint, jacobianPoint, jacobianQuaternion);
1473     }
1474 
1475 
1476     /**
1477      * Rotates a 3D point using the origin of coordinates as the axis of
1478      * rotation.
1479      * Point will be rotated by the amount of rotation contained in this
1480      * quaternion instance.
1481      *
1482      * @param inputPoint  Input point to be rotated.
1483      * @param resultPoint Rotated point.
1484      * @see <a href="https://github.com/joansola/slamtb">qRot.m at https://github.com/joansola/slamtb</a>
1485      */
1486     @Override
1487     public void rotate(final Point3D inputPoint, final Point3D resultPoint) {
1488         rotate(inputPoint, resultPoint, null, null);
1489     }
1490 
1491     /**
1492      * Returns a 3D point containing a rotated version of provided point.
1493      * Point will be rotated using the origin of the coordinates as the axis of
1494      * rotation.
1495      * Point will be rotated by the amount of rotation contained in this
1496      * quaternion instance.
1497      *
1498      * @param point Point to be rotated.
1499      * @return Rotated point.
1500      * @see <a href="https://github.com/joansola/slamtb">qRot.m at https://github.com/joansola/slamtb</a>
1501      */
1502     @Override
1503     public Point3D rotate(final Point3D point) {
1504         final var result = new HomogeneousPoint3D();
1505         rotate(point, result);
1506         return result;
1507     }
1508 
1509     /**
1510      * Converts rotation matrix into a quaternion.
1511      *
1512      * @param r      a rotation matrix to be converted from.
1513      * @param result quaternion where result is stored.
1514      * @throws IllegalArgumentException if provided matrix is not 3x3
1515      * @see <a href="https://github.com/joansola/slamtb">R2q.m at https://github.com/joansola/slamtb</a>
1516      */
1517     public static void matrixRotationToQuaternion(final Matrix r, final Quaternion result) {
1518         if (r.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
1519                 || r.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS) {
1520             throw new IllegalArgumentException("rotation matrix must be 3x3");
1521         }
1522 
1523         final var trace = com.irurueta.algebra.Utils.trace(r) + 1.0;
1524         double s;
1525         double a;
1526         double b;
1527         double c;
1528         double d;
1529 
1530         if (trace > TRACE_THRESHOLD) {
1531             // to avoid large distortions
1532             s = 2.0 * Math.sqrt(trace);
1533             a = 0.25 * s;
1534             b = (r.getElementAt(1, 2) - r.getElementAt(2, 1)) / s;
1535             c = (r.getElementAt(2, 0) - r.getElementAt(0, 2)) / s;
1536             d = (r.getElementAt(0, 1) - r.getElementAt(1, 0)) / s;
1537         } else {
1538             if (r.getElementAt(0, 0) > r.getElementAt(1, 1)
1539                     && r.getElementAt(0, 0) > r.getElementAt(2, 2)) {
1540                 // column 1:
1541                 // tested with R2 = diag([1 -1 -1])
1542 
1543                 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(0, 0) - r.getElementAt(1, 1)
1544                         - r.getElementAt(2, 2));
1545                 a = (r.getElementAt(1, 2) - r.getElementAt(2, 1)) / s;
1546                 b = 0.25 * s;
1547                 c = (r.getElementAt(0, 1) + r.getElementAt(1, 0)) / s;
1548                 d = (r.getElementAt(2, 0) + r.getElementAt(0, 2)) / s;
1549             } else if (r.getElementAt(1, 1) > r.getElementAt(2, 2)) {
1550                 // column 2:
1551                 // tested with R3 = [0 1 0; 1 0 0; 0 0 -1]
1552 
1553                 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(1, 1) - r.getElementAt(0, 0)
1554                         - r.getElementAt(2, 2));
1555                 a = (r.getElementAt(2, 0) - r.getElementAt(0, 2)) / s;
1556                 b = (r.getElementAt(0, 1) + r.getElementAt(1, 0)) / s;
1557                 c = 0.25 * s;
1558                 d = (r.getElementAt(1, 2) + r.getElementAt(2, 1)) / s;
1559             } else {
1560                 // column 3:
1561                 // tested with R4 = [-1 0 0; 0 0 1; 0 1 0]
1562 
1563                 s = 2.0 * Math.sqrt(1.0 + r.getElementAt(2, 2)
1564                         - r.getElementAt(0, 0) - r.getElementAt(1, 1));
1565                 a = (r.getElementAt(0, 1) - r.getElementAt(1, 0)) / s;
1566                 b = (r.getElementAt(2, 0) + r.getElementAt(0, 2)) / s;
1567                 c = (r.getElementAt(1, 2) + r.getElementAt(2, 1)) / s;
1568                 d = 0.25 * s;
1569             }
1570         }
1571 
1572         result.a = a;
1573         result.b = -b;
1574         result.c = -c;
1575         result.d = -d;
1576         result.normalized = false;
1577     }
1578 
1579     /**
1580      * Converts 3D matrix rotation into a quaternion.
1581      *
1582      * @param rotation a 3D matrix rotation to be converted from.
1583      * @param result   quaternion where result is stored.
1584      * @see <a href="https://github.com/joansola/slamtb">R2q.m at https://github.com/joansola/slamtb</a>
1585      */
1586     public static void matrixRotationToQuaternion(final MatrixRotation3D rotation, final Quaternion result) {
1587         matrixRotationToQuaternion(rotation.internalMatrix, result);
1588     }
1589 
1590     /**
1591      * Sets quaternion values associated to provided rotation.
1592      *
1593      * @param matrix a rotation matrix.
1594      * @throws IllegalArgumentException if provided matrix is not 3x3.
1595      * @see <a href="https://github.com/joansola/slamtb">R2q.m at https://github.com/joansola/slamtb</a>
1596      */
1597     public void setFromMatrixRotation(final Matrix matrix) {
1598         matrixRotationToQuaternion(matrix, this);
1599     }
1600 
1601     /**
1602      * Sets quaternion values associated to provided rotation.
1603      *
1604      * @param rotation a rotation to be converted into a quaternion.
1605      * @see <a href="https://github.com/joansola/slamtb">R2q.m at https://github.com/joansola/slamtb</a>
1606      */
1607     public final void setFromMatrixRotation(final MatrixRotation3D rotation) {
1608         matrixRotationToQuaternion(rotation, this);
1609     }
1610 
1611     /**
1612      * Converts a rotation vector (rotation axis having a norm equal to the
1613      * rotation angle) into a normalized rotation axis and its corresponding
1614      * rotation angle.
1615      *
1616      * @param rotationVector         input rotation vector to be converted.
1617      * @param axis                   obtained normalized rotation axis.
1618      * @param jacobianAlpha          jacobian wrt of angle.
1619      * @param jacobianRotationVector jacobian wrt of rotation vector.
1620      * @return rotation angle.
1621      * @throws IllegalArgumentException if provided rotation vector length is
1622      *                                  not 3, jacobian of angle is not 1x3 or jacobian of rotation vector is
1623      *                                  not 3x3.
1624      * @see <a href="https://github.com/joansola/slamtb">v2au.m at https://github.com/joansola/slamtb</a>
1625      */
1626     public static double rotationVectorToRotationAxisAndAngle(
1627             final double[] rotationVector, final double[] axis, final Matrix jacobianAlpha,
1628             final Matrix jacobianRotationVector) {
1629         if (rotationVector.length != AxisRotation3D.AXIS_PARAMS) {
1630             throw new IllegalArgumentException("rotation vector length must be 3");
1631         }
1632 
1633         if (jacobianAlpha != null && (jacobianAlpha.getRows() != 1 || jacobianAlpha.getColumns() != N_ANGLES)) {
1634             throw new IllegalArgumentException("jacobian alpha must be 1x3");
1635         }
1636 
1637         if (jacobianRotationVector != null && (jacobianRotationVector.getRows() != N_ANGLES
1638                 || jacobianRotationVector.getColumns() != N_ANGLES)) {
1639             throw new IllegalArgumentException("jacobian rotation vector must be 3x3");
1640         }
1641 
1642         var alpha = com.irurueta.algebra.Utils.normF(rotationVector);
1643 
1644         if (alpha > AXIS_NORM_THRESHOLD) {
1645             ArrayUtils.multiplyByScalar(rotationVector, 1.0 / alpha, axis);
1646 
1647             if (jacobianAlpha != null) {
1648                 jacobianAlpha.setSubmatrix(0, 0, 0,
1649                         axis.length - 1, axis);
1650             }
1651 
1652             if (jacobianRotationVector != null) {
1653                 jacobianRotationVector.setElementAt(0, 0, 1.0 / alpha - axis[0] * axis[0] / alpha);
1654                 jacobianRotationVector.setElementAt(1, 0, -axis[0] / alpha * axis[1]);
1655                 jacobianRotationVector.setElementAt(2, 0, -axis[0] / alpha * axis[2]);
1656 
1657                 jacobianRotationVector.setElementAt(0, 1, -axis[0] / alpha * axis[1]);
1658                 jacobianRotationVector.setElementAt(1, 1, 1.0 / alpha - axis[1] * axis[1] / alpha);
1659                 jacobianRotationVector.setElementAt(2, 1, -axis[1] / alpha * axis[2]);
1660 
1661                 jacobianRotationVector.setElementAt(0, 2, -axis[0] / alpha * axis[2]);
1662                 jacobianRotationVector.setElementAt(1, 2, -axis[1] / alpha * axis[2]);
1663                 jacobianRotationVector.setElementAt(2, 2, 1.0 / alpha - axis[2] * axis[2] / alpha);
1664             }
1665 
1666         } else {
1667             alpha = 0.0;
1668             Arrays.fill(axis, 0.0);
1669 
1670             if (jacobianAlpha != null) {
1671                 jacobianAlpha.initialize(0.0);
1672             }
1673 
1674             if (jacobianRotationVector != null) {
1675                 jacobianRotationVector.initialize(0.0);
1676             }
1677         }
1678 
1679         return alpha;
1680     }
1681 
1682     /**
1683      * Converts a rotation vector (rotation axis having a norm equal to the
1684      * rotation angle) into a normalized rotation axis and its corresponding
1685      * rotation angle.
1686      *
1687      * @param rotationVector input rotation vector to be converted.
1688      * @param axis           obtained normalized rotation axis.
1689      * @return rotation angle.
1690      * @throws IllegalArgumentException if provided rotation vector length is
1691      *                                  not 3.
1692      * @see <a href="https://github.com/joansola/slamtb">v2au.m at https://github.com/joansola/slamtb</a>
1693      */
1694     public static double rotationVectorToRotationAxisAndAngle(final double[] rotationVector, final double[] axis) {
1695         return rotationVectorToRotationAxisAndAngle(rotationVector, axis, null, null);
1696     }
1697 
1698     /**
1699      * Converts a rotation vector (rotation axis having a norm equal to the
1700      * rotation angle) into a quaternion, and stores the corresponding jacobian
1701      * of the quaternion respect to the vector if provided.
1702      *
1703      * @param rotationVector input rotation vector to be converted.
1704      * @param result         quaternion where result will be stored.
1705      * @param jacobian       if provided, matrix where jacobian of the quaternion
1706      *                       respect to the vector will be stored. Must be 4x3.
1707      * @throws IllegalArgumentException if provided rotation vector is not
1708      *                                  length 3 or if provided jacobian matrix is not 4x3.
1709      * @see <a href="https://github.com/joansola/slamtb">v2q.m at https://github.com/joansola/slamtb</a>
1710      */
1711     public static void rotationVectorToQuaternion(
1712             final double[] rotationVector, final Quaternion result, final Matrix jacobian) {
1713         if (rotationVector.length != AxisRotation3D.AXIS_PARAMS) {
1714             throw new IllegalArgumentException("rotation vector length must be 3");
1715         }
1716         if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_ANGLES)) {
1717             throw new IllegalArgumentException("jacobian must be 4x3");
1718         }
1719 
1720         final var axis = new double[AxisRotation3D.AXIS_PARAMS];
1721         if (jacobian == null) {
1722             final var alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis);
1723             result.setFromAxisAndRotation(axis, alpha);
1724         } else {
1725             var alpha = com.irurueta.algebra.Utils.normF(rotationVector);
1726 
1727             if (alpha < LARGE_AXIS_NORM_THRESHOLD) {
1728                 // use small signal approximation
1729                 result.a = 1 - alpha * alpha / 8.0;
1730                 result.b = rotationVector[0] / 2.0;
1731                 result.c = rotationVector[1] / 2.0;
1732                 result.d = rotationVector[2] / 2.0;
1733                 result.normalized = false;
1734 
1735                 jacobian.setElementAt(0, 0, -0.25 * rotationVector[0]);
1736                 jacobian.setElementAt(0, 1, -0.25 * rotationVector[1]);
1737                 jacobian.setElementAt(0, 2, -0.25 * rotationVector[2]);
1738 
1739                 jacobian.setElementAt(1, 0, 0.5);
1740                 jacobian.setElementAt(1, 1, 0.0);
1741                 jacobian.setElementAt(1, 2, 0.0);
1742 
1743                 jacobian.setElementAt(2, 0, 0.0);
1744                 jacobian.setElementAt(2, 1, 0.5);
1745                 jacobian.setElementAt(2, 2, 0.0);
1746 
1747                 jacobian.setElementAt(3, 0, 0.0);
1748                 jacobian.setElementAt(3, 1, 0.0);
1749                 jacobian.setElementAt(3, 2, 0.5);
1750             } else {
1751                 try {
1752                     // Av
1753                     final var jacobianAlpha = new Matrix(1, N_ANGLES);
1754                     // Uv
1755                     final var jacobianRotationVector = new Matrix(N_ANGLES, N_ANGLES);
1756                     alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis, jacobianAlpha,
1757                             jacobianRotationVector);
1758 
1759                     // Qa
1760                     final var jacobianOfTheta = new Matrix(N_PARAMS, 1);
1761                     // Qu
1762                     final var jacobianOfAxis = new Matrix(N_PARAMS, N_ANGLES);
1763                     result.setFromAxisAndRotation(axis, alpha, jacobianOfTheta, jacobianOfAxis);
1764 
1765                     // Qv = Qa * Av + Qu * Uv
1766                     // Qa * Av
1767                     jacobianOfTheta.multiply(jacobianAlpha);
1768                     // Qu * Uv
1769                     jacobianOfAxis.multiply(jacobianRotationVector);
1770 
1771                     jacobian.copyFrom(jacobianOfTheta);
1772                     jacobian.add(jacobianOfAxis);
1773 
1774                 } catch (final WrongSizeException e) {
1775                     throw new IllegalArgumentException(e);
1776                 }
1777             }
1778         }
1779     }
1780 
1781     /**
1782      * Converts a rotation vector (rotation axis having a norm equal to the
1783      * rotation angle) into a quaternion.
1784      *
1785      * @param rotationVector input rotation vector to be converted.
1786      * @param result         quaternion where result will be stored.
1787      * @throws IllegalArgumentException if provided rotation vector is not
1788      *                                  length 3.
1789      * @see <a href="https://github.com/joansola/slamtb">v2q.m at https://github.com/joansola/slamtb</a>
1790      */
1791     public static void rotationVectorToQuaternion(final double[] rotationVector, final Quaternion result) {
1792         rotationVectorToQuaternion(rotationVector, result, null);
1793     }
1794 
1795     /**
1796      * Sets values of this quaternion from provided rotation vector.
1797      * A rotation vector is a rotation axis having a norm equal to the rotation
1798      * angle.
1799      *
1800      * @param rotationVector input rotation vector to obtain quaternion values
1801      *                       from.
1802      * @throws IllegalArgumentException if provided rotation vector does not
1803      *                                  have length 3.
1804      * @see <a href="https://github.com/joansola/slamtb">v2q.m at https://github.com/joansola/slamtb</a>
1805      */
1806     public void setFromRotationVector(final double[] rotationVector) {
1807         rotationVectorToQuaternion(rotationVector, this);
1808     }
1809 
1810     /**
1811      * Converts a rotation vector into a rotation matrix.
1812      * A rotation vector is a rotation axis having a norm equal to the rotation
1813      * angle.
1814      *
1815      * @param rotationVector a rotation vector to be converted into a 3D matrix
1816      *                       rotation.
1817      * @param result         matrix where result is stored.
1818      * @throws IllegalArgumentException if provided rotation vector does not
1819      *                                  have length 3.
1820      * @see <a href="https://github.com/joansola/slamtb">v2R.m at https://github.com/joansola/slamtb</a>
1821      */
1822     public static void rotationVectorToMatrixRotation(final double[] rotationVector, final Matrix result) {
1823         final var axis = new double[AxisRotation3D.AXIS_PARAMS];
1824         final var alpha = rotationVectorToRotationAxisAndAngle(rotationVector, axis);
1825         final var r = new AxisRotation3D(axis, alpha);
1826         r.asInhomogeneousMatrix(result);
1827     }
1828 
1829     /**
1830      * Converts a rotation vector into a rotation matrix.
1831      * A rotation vector is a rotation axis having a norm equal to the rotation
1832      * angle.
1833      *
1834      * @param rotationVector a rotation vector to be converted into a 3D matrix
1835      *                       rotation.
1836      * @param result         3D matrix rotation where result is stored.
1837      * @throws IllegalArgumentException if provided rotation vector does not
1838      *                                  have length 3.
1839      * @see <a href="https://github.com/joansola/slamtb">v2R.m at https://github.com/joansola/slamtb</a>
1840      */
1841     public static void rotationVectorToMatrixRotation(final double[] rotationVector, final MatrixRotation3D result) {
1842         rotationVectorToMatrixRotation(rotationVector, result.internalMatrix);
1843     }
1844 
1845     /**
1846      * Returns type of this rotation.
1847      *
1848      * @return Type of this rotation.
1849      */
1850     @Override
1851     public Rotation3DType getType() {
1852         return Rotation3DType.QUATERNION;
1853     }
1854 
1855     /**
1856      * Sets the axis and rotation of this instance.
1857      * Once set, points will rotate around provided axis an amount equal to
1858      * provided rotation angle in radians.
1859      * Note: to avoid numerical instabilities and improve accuracy, axis
1860      * coordinates should be normalized (e.g. norm equal to 1).
1861      *
1862      * @param axisX X coordinate of rotation axis.
1863      * @param axisY Y coordinate of rotation axis.
1864      * @param axisZ Z coordinate of rotation axis.
1865      * @param theta Amount of rotation in radians.
1866      */
1867     @Override
1868     public void setAxisAndRotation(
1869             final double axisX, final double axisY, final double axisZ, final double theta) {
1870         setFromAxisAndRotation(axisX, axisY, axisZ, theta);
1871     }
1872 
1873     /**
1874      * Returns rotation axis corresponding to this instance.
1875      * Result is stored in provided axis array, which must have length 3.
1876      *
1877      * @param axis Array where axis coordinates will be stored.
1878      * @throws IllegalArgumentException Raised if provided array does not have
1879      *                                  length 3.
1880      */
1881     @Override
1882     public void rotationAxis(final double[] axis) {
1883         toAxisAndRotationAngle(axis);
1884     }
1885 
1886     /**
1887      * Returns rotation amount or angle in radians around the rotation axis
1888      * associated to this instance.
1889      *
1890      * @return Rotation angle in radians.
1891      */
1892     @Override
1893     public double getRotationAngle() {
1894         // norm of rotation axis
1895         final var n = Math.sqrt(b * b + c * c + d * d);
1896         return 2.0 * Math.atan2(n, a);
1897     }
1898 
1899     /**
1900      * Returns this 3D rotation instance expressed as a 3x3 inhomogeneous
1901      * matrix.
1902      * This is equivalent to call getInternalMatrix().
1903      *
1904      * @return Rotation matrix expressed in inhomogeneous coordinates.
1905      */
1906     @Override
1907     public Matrix asInhomogeneousMatrix() {
1908         Matrix m = null;
1909         try {
1910             m = new Matrix(MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS,
1911                     MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS);
1912             toMatrixRotation(m);
1913         } catch (final WrongSizeException ignore) {
1914             // never thrown
1915         }
1916         return m;
1917     }
1918 
1919     /**
1920      * Sets into provided Matrix instance this 3D rotation expressed as a
1921      * 3x3 inhomogeneous matrix.
1922      *
1923      * @param result Matrix where rotation will be set.
1924      * @throws IllegalArgumentException Raised if provided instance does not
1925      *                                  have size 3x3.
1926      */
1927     @Override
1928     public void asInhomogeneousMatrix(final Matrix result) {
1929         toMatrixRotation(result);
1930     }
1931 
1932     /**
1933      * Returns this 3D rotation instance expressed as a 4x4 homogeneous matrix.
1934      *
1935      * @return Rotation matrix expressed in homogeneous coordinates.
1936      */
1937     @Override
1938     public Matrix asHomogeneousMatrix() {
1939         Matrix m = null;
1940         try {
1941             m = new Matrix(HOM_COORDS, HOM_COORDS);
1942             asHomogeneousMatrix(m);
1943         } catch (final WrongSizeException ignore) {
1944             // never thrown
1945         }
1946         return m;
1947     }
1948 
1949     /**
1950      * Sets into provided Matrix instance this 3D rotation expressed as a
1951      * 4x4 homogeneous matrix.
1952      *
1953      * @param result Matrix where rotation will be set.
1954      * @throws IllegalArgumentException Raised if provided instance does not
1955      *                                  have size 4x4.
1956      */
1957     @Override
1958     public void asHomogeneousMatrix(final Matrix result) {
1959         result.initialize(0.0);
1960         result.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
1961         result.setSubmatrix(0, 0, INHOM_COORDS - 1,
1962                 INHOM_COORDS - 1, asInhomogeneousMatrix());
1963     }
1964 
1965     /**
1966      * Sets amount of rotation from provided inhomogeneous rotation matrix.
1967      * Provided matrix must be orthogonal (i.e. squared, non-singular, it's
1968      * transpose must be its inverse) and must have determinant equal to 1.
1969      * Provided matrix must also have size 3x3.
1970      *
1971      * @param m         Provided rotation matrix.
1972      * @param threshold Threshold to determine whether matrix is orthonormal.
1973      * @throws IllegalArgumentException Raised if provided threshold is
1974      *                                  negative.
1975      *                                  {@link #isValidRotationMatrix(Matrix)}
1976      */
1977     @Override
1978     public void fromInhomogeneousMatrix(final Matrix m, final double threshold) {
1979         setFromMatrixRotation(m);
1980     }
1981 
1982     /**
1983      * Sets amount of rotation from provided homogeneous rotation matrix.
1984      * Provided matrix must be orthogonal (i.e. squared, non-singular, it's
1985      * transpose must be its inverse) and must have determinant equal to 1.
1986      * Provided matrix must also have size 4x4, and its last row and column must
1987      * be zero, except for element in last row and column which must be 1.
1988      *
1989      * @param m         Provided rotation matrix.
1990      * @param threshold Threshold to determine whether matrix is orthonormal.
1991      * @throws IllegalArgumentException Raised if provided threshold is
1992      *                                  negative.
1993      *                                  {@link #isValidRotationMatrix(Matrix)}
1994      */
1995     @Override
1996     public void fromHomogeneousMatrix(final Matrix m, final double threshold) {
1997         setFromMatrixRotation(m.getSubmatrix(0, 0, INHOM_COORDS - 1,
1998                 INHOM_COORDS - 1));
1999     }
2000 
2001     /**
2002      * Inverts a quaternion so that q * q^-1 = 1.
2003      *
2004      * @param q      quaternion to be inverted.
2005      * @param result quaternion instance where the result will be stored.
2006      */
2007     public static void inverse(final Quaternion q, final Quaternion result) {
2008         // the inverse is the conjugate divided by the quaternion square norm
2009         final var sqrNorm = q.a * q.a + q.b * q.b + q.c * q.c + q.d * q.d;
2010         q.conjugate(result);
2011         result.a /= sqrNorm;
2012         result.b /= sqrNorm;
2013         result.c /= sqrNorm;
2014         result.d /= sqrNorm;
2015         result.normalized = false;
2016     }
2017 
2018     /**
2019      * Inverts a quaternion so that q * q^-1 = 1.
2020      *
2021      * @param q quaternion to be inverted.
2022      * @return a new quaternion containing the inverse.
2023      */
2024     public static Quaternion inverseAndReturnNew(final Quaternion q) {
2025         final var result = new Quaternion();
2026         inverse(q, result);
2027         return result;
2028     }
2029 
2030     /**
2031      * Inverts this quaternion instance so that q * q^-1 = 1.
2032      *
2033      * @param result instance where quaternion inverse is stored.
2034      */
2035     public void inverse(final Quaternion result) {
2036         inverse(this, result);
2037     }
2038 
2039     /**
2040      * Inverts this quaternion instance so that q * q^-1 = 1.
2041      *
2042      * @return a new quaternion containing the inverse of this quaternion.
2043      */
2044     public Quaternion inverseAndReturnNew() {
2045         final var result = new Quaternion();
2046         inverse(result);
2047         return result;
2048     }
2049 
2050     /**
2051      * Inverts this quaternion.
2052      */
2053     public void inverse() {
2054         inverse(this);
2055     }
2056 
2057     /**
2058      * Returns a 3D rotation which is inverse to this instance.
2059      * In other words, the combination of this rotation with its inverse
2060      * produces no change.
2061      *
2062      * @return Inverse 3D rotation.
2063      */
2064     @Override
2065     public Rotation3D inverseRotationAndReturnNew() {
2066         final var q = new Quaternion();
2067         inverseRotation(q);
2068         return q;
2069     }
2070 
2071     /**
2072      * Inverts this quaternion instance so that q * q^-1 = 1.
2073      *
2074      * @param result instance where quaternion inverse is stored.
2075      */
2076     public void inverseRotation(final Quaternion result) {
2077         inverse(result);
2078     }
2079 
2080     /**
2081      * Sets into provided Rotation3D instance a rotation inverse to this
2082      * instance.
2083      * The combination of this rotation with its inverse produces no change.
2084      *
2085      * @param result Instance where inverse rotation will be set.
2086      */
2087     @Override
2088     public void inverseRotation(final Rotation3D result) {
2089         final var inverse = inverseAndReturnNew();
2090         result.fromRotation(inverse);
2091     }
2092 
2093     /**
2094      * Reverses the rotation of this instance.
2095      */
2096     @Override
2097     public void inverseRotation() {
2098         inverse();
2099     }
2100 
2101     /**
2102      * Combines provided quaternions q1 and q2 to produce a resulting quaternion
2103      * equivalent to the combined rotation of both quaternions.
2104      *
2105      * @param q1     1st quaternion.
2106      * @param q2     2nd quaternion.
2107      * @param result combined quaternion where result is stored.
2108      */
2109     public static void combine(final Quaternion q1, final Quaternion q2, final Quaternion result) {
2110         product(q1, q2, result);
2111     }
2112 
2113     /**
2114      * Combines provided quaternion with this quaternion and returns the result
2115      * as a new quaternion instance.
2116      *
2117      * @param q input quaternion to be combined.
2118      * @return combined quaternion, which is equal to the multiplication of
2119      * quaternions.
2120      */
2121     public Quaternion combineAndReturnNew(final Quaternion q) {
2122         final var result = new Quaternion();
2123         combine(this, q, result);
2124         return result;
2125     }
2126 
2127     /**
2128      * Combines provided quaternion into this quaternion, resulting in the
2129      * multiplication of both quaternion representations.
2130      *
2131      * @param q input quaternion to be combined.
2132      */
2133     public void combine(final Quaternion q) {
2134         combine(this, q, this);
2135     }
2136 
2137     /**
2138      * Combines provided rotation with this quaternion and returns the result as
2139      * a new quaternion instance.
2140      *
2141      * @param rotation input rotation to be combined.
2142      * @return combined rotation, which is equal to the multiplication of the
2143      * internal quaternion representations.
2144      */
2145     @Override
2146     public Rotation3D combineAndReturnNew(final Rotation3D rotation) {
2147         return combineAndReturnNew(rotation.toQuaternion());
2148     }
2149 
2150     /**
2151      * Combines provided rotation into this quaternion, resulting in the
2152      * multiplication of both quaternion representations.
2153      *
2154      * @param rotation input rotation to be combined.
2155      */
2156     @Override
2157     public void combine(final Rotation3D rotation) {
2158         combine(rotation.toQuaternion());
2159     }
2160 
2161     /**
2162      * Sets values of this rotation from a 3D matrix rotation.
2163      *
2164      * @param rot 3D matrix rotation to set values from.
2165      */
2166     @Override
2167     public void fromRotation(final MatrixRotation3D rot) {
2168         setFromMatrixRotation(rot);
2169     }
2170 
2171     /**
2172      * Sets values of this rotation from a 3D axis rotation.
2173      *
2174      * @param rot an axis rotation to set values from.
2175      */
2176     @Override
2177     public void fromRotation(final AxisRotation3D rot) {
2178         setFromAxisAndRotation(rot);
2179     }
2180 
2181     /**
2182      * Sets values of this rotation from a quaternion.
2183      *
2184      * @param q a quaternion to set values from.
2185      */
2186     @Override
2187     public void fromRotation(final Quaternion q) {
2188         a = q.a;
2189         b = q.b;
2190         c = q.c;
2191         d = q.d;
2192         normalized = q.normalized;
2193     }
2194 
2195     /**
2196      * Converts this 3D rotation into a quaternion storing the result into
2197      * provided instance.
2198      *
2199      * @param result instance where result will be stored.
2200      */
2201     @Override
2202     public void toQuaternion(final Quaternion result) {
2203         result.fromQuaternion(this);
2204     }
2205 
2206     /**
2207      * Indicates whether quaternion is already normalized or not.
2208      *
2209      * @return true if quaternion is normalized, false otherwise.
2210      */
2211     public boolean isNormalized() {
2212         return normalized;
2213     }
2214 
2215     /**
2216      * Normalizes this quaternion if not already normalized.
2217      */
2218     public void normalize() {
2219         if (!normalized) {
2220             final var norm = Math.sqrt(a * a + b * b + c * c + d * d);
2221             internalNormalize(norm);
2222         }
2223     }
2224 
2225     /**
2226      * Normalizes this quaternion if not already normalized and stores the
2227      * corresponding jacobian into provided matrix (if provided).
2228      *
2229      * @param jacobian matrix where jacobian will be stored (if provided). Must
2230      *                 be 4x4.
2231      * @throws IllegalArgumentException if provided jacobian is not 4x4.
2232      */
2233     public void normalize(final Matrix jacobian) {
2234         if (jacobian != null && (jacobian.getRows() != N_PARAMS || jacobian.getColumns() != N_PARAMS)) {
2235             throw new IllegalArgumentException("jacobian must be 4x4");
2236         }
2237 
2238         final var aValue = this.a;
2239         final var bValue = this.b;
2240         final var cValue = this.c;
2241         final var dValue = this.d;
2242         final var norm = Math.sqrt(aValue * aValue + bValue * bValue + cValue * cValue + dValue * dValue);
2243 
2244         internalNormalize(norm);
2245 
2246         if (jacobian != null) {
2247             final var norm3 = norm * norm * norm;
2248 
2249             jacobian.setElementAt(0, 0, (bValue * bValue + cValue * cValue + dValue * dValue)
2250                     / norm3);
2251             jacobian.setElementAt(1, 0, -aValue / norm3 * bValue);
2252             jacobian.setElementAt(2, 0, -aValue / norm3 * cValue);
2253             jacobian.setElementAt(3, 0, -aValue / norm3 * dValue);
2254 
2255             jacobian.setElementAt(0, 1, -aValue / norm3 * bValue);
2256             jacobian.setElementAt(1, 1, (aValue * aValue + cValue * cValue + dValue * dValue)
2257                     / norm3);
2258             jacobian.setElementAt(2, 1, -bValue / norm3 * cValue);
2259             jacobian.setElementAt(3, 1, -bValue / norm3 * dValue);
2260 
2261             jacobian.setElementAt(0, 2, -aValue / norm3 * cValue);
2262             jacobian.setElementAt(1, 2, -bValue / norm3 * cValue);
2263             jacobian.setElementAt(2, 2, (aValue * aValue + bValue * bValue + dValue * dValue)
2264                     / norm3);
2265             jacobian.setElementAt(3, 2, -cValue / norm3 * dValue);
2266 
2267             jacobian.setElementAt(0, 3, -aValue / norm3 * dValue);
2268             jacobian.setElementAt(1, 3, -bValue / norm3 * dValue);
2269             jacobian.setElementAt(2, 3, -cValue / norm3 * dValue);
2270             jacobian.setElementAt(3, 3, (aValue * aValue + bValue * bValue + cValue * cValue)
2271                     / norm3);
2272         }
2273     }
2274 
2275     /**
2276      * Computes a linear interpolation between this quaternion and provided quaternion using
2277      * provided value as the interpolation ratio.
2278      *
2279      * @param q quaternion to interpolate.
2280      * @param t interpolation ratio. Must be a value between 0.0 and 1.0, both
2281      *          included. The closer the value is to 0.0, the more similar result will
2282      *          be to this instance. Conversely, the closer the value is to 1.0, the
2283      *          more similar result will be to q.
2284      * @return a new interpolated quaternion instance.
2285      * @throws IllegalArgumentException if provided interpolation ratio is not between 0.0
2286      *                                  and 1.0, both included.
2287      */
2288     public Quaternion slerpAndReturnNew(final Quaternion q, final double t) {
2289         final var result = new Quaternion();
2290         slerp(q, t, result);
2291         return result;
2292     }
2293 
2294     /**
2295      * Computes a linear interpolation between this quaternion and provided quaternion using
2296      * provided value as the interpolation ratio and stores the result into provided result
2297      * quaternion.
2298      *
2299      * @param q      quaternion to interpolate.
2300      * @param t      interpolation ratio. Must be a value between 0.0 and 1.0, both
2301      *               included. The closer the value is to 0.0, the more similar result will
2302      *               be to this instance. Conversely, the closer the value is to 1.0, the
2303      *               more similar result will be to q.
2304      * @param result instance where interpolated quaternion will be stored.
2305      * @throws IllegalArgumentException if provided interpolation ratio is not between 0.0
2306      *                                  and 1.0, both included.
2307      */
2308     public void slerp(final Quaternion q, final double t, final Quaternion result) {
2309         slerp(this, q, t, result);
2310     }
2311 
2312     /**
2313      * Computes a linear interpolation between provided quaternions using provided value
2314      * as the interpolation ratio.
2315      *
2316      * @param q1 1st quaternion to interpolate.
2317      * @param q2 2nd quaternion to interpolate.
2318      * @param t  interpolation ratio. Must be a value between 0.0 and 1.0, both
2319      *           included. The closer the value is to 0.0, the more similar result will
2320      *           be to q1. Conversely, the closer the value is to 1.0, the more similar
2321      *           result will be to q2.
2322      * @return a new interpolated quaternion instance.
2323      * @throws IllegalArgumentException if provided interpolation ratio is not between 0.0
2324      *                                  and 1.0, both included.
2325      */
2326     public static Quaternion slerpAndReturnNew(final Quaternion q1, final Quaternion q2, final double t) {
2327         final var result = new Quaternion();
2328         slerp(q1, q2, t, result);
2329         return result;
2330     }
2331 
2332     /**
2333      * Computes a linear interpolation between provided quaternions using provided value
2334      * as the interpolation ratio, and stores the result into provided result quaternion.
2335      *
2336      * @param q1     1st quaternion to interpolate.
2337      * @param q2     2nd quaternion to interpolate.
2338      * @param t      interpolation ratio. Must be a value between 0.0 and 1.0, both
2339      *               included. The closer the value is to 0.0, the more similar result will
2340      *               be to q1. Conversely, the closer the value is to 1.0, the more similar
2341      *               result will be to q2.
2342      * @param result instance where interpolated quaternion will be stored.
2343      * @throws IllegalArgumentException if provided interpolation ratio is not between 0.0
2344      *                                  and 1.0, both included.
2345      */
2346     public static void slerp(final Quaternion q1, final Quaternion q2, final double t, final Quaternion result) {
2347         if (t < 0.0 || t > 1.0) {
2348             throw new IllegalArgumentException();
2349         }
2350 
2351         // only unit quaternions are valid rotations.
2352         // normalize to avoid undefined behavior.
2353         q1.normalize();
2354         q2.normalize();
2355 
2356         // calculate angle between input quaternions
2357         var dot = q1.a * q2.a + q1.b * q2.b + q1.c * q2.c + q1.d * q2.d;
2358 
2359         // if q1 = q2 or q1 = -q2 (both quaternions are equal), then the angle
2360         // between input quaternions is theta0 = 0.
2361         // To avoid singularity caused by sinTheta0, we return q1
2362         if (Math.abs(dot) >= 1.0) {
2363             result.a = q1.a;
2364             result.b = q1.b;
2365             result.c = q1.c;
2366             result.d = q1.d;
2367             return;
2368         }
2369 
2370         // if the dot product is negative, slerp won't take the shorter path.
2371         // note that q2 and -q2 are equivalent when the negation is applied to all four
2372         // components. Fix by reversing one quaternion.
2373         Quaternion q2b;
2374         if (dot < 0.0) {
2375             q2b = new Quaternion(-q2.a, -q2.b, -q2.c, -q2.d);
2376             dot = -dot;
2377         } else {
2378             q2b = q2;
2379         }
2380 
2381         final var theta0 = Math.acos(dot);
2382         final var theta = theta0 * t;
2383         final var sinTheta = Math.sin(theta);
2384         final var sinTheta0 = Math.sin(theta0);
2385 
2386         final var s2 = sinTheta / sinTheta0;
2387         // line below is equal to sin(theta0 - theta) / sinTheta0
2388         final var s1 = Math.cos(theta) - dot * s2;
2389 
2390         result.a = s1 * q1.a + s2 * q2b.a;
2391         result.b = s1 * q1.b + s2 * q2b.b;
2392         result.c = s1 * q1.c + s2 * q2b.c;
2393         result.d = s1 * q1.d + s2 * q2b.d;
2394     }
2395 
2396     /**
2397      * Normalizes this quaternion if not already normalized.
2398      *
2399      * @param norm norm to normalize this quaternion with.
2400      */
2401     private void internalNormalize(final double norm) {
2402         if (!normalized) {
2403             a /= norm;
2404             b /= norm;
2405             c /= norm;
2406             d /= norm;
2407             normalized = true;
2408         }
2409     }
2410 }