View Javadoc
1   /*
2    * Copyright (C) 2012 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.Matrix;
19  import com.irurueta.algebra.WrongSizeException;
20  
21  /**
22   * Utility methods related to rotations.
23   */
24  @SuppressWarnings("DuplicatedCode")
25  public class RotationUtils {
26  
27      /**
28       * Number of components of angular rates (angular speed).
29       */
30      public static final int N_ANGULAR_RATES = 3;
31  
32      /**
33       * Size of skew symmetric matrix omega.
34       */
35      public static final int SKEW_MATRIX_SIZE = 4;
36  
37      /**
38       * Constructor.
39       */
40      private RotationUtils() {
41      }
42  
43      /**
44       * Skew symmetric matrix omega from angular rates vector w.
45       *
46       * @param w1     angular rate from x-axis.
47       * @param w2     angular rate from y-axis.
48       * @param w3     angular rate from z-axis.
49       * @param result instance where result will be stored.
50       * @throws IllegalArgumentException if provided matrix is not 4x4.
51       * @see <a href="https://github.com/joansola/slamtb">w2omega.m at https://github.com/joansola/slamtb</a>
52       */
53      public static void w2omega(final double w1, final double w2, final double w3, final Matrix result) {
54          if (result.getRows() != SKEW_MATRIX_SIZE || result.getColumns() != SKEW_MATRIX_SIZE) {
55              throw new IllegalArgumentException("result must be 4x4");
56          }
57  
58          result.setElementAt(0, 0, 0.0);
59          result.setElementAt(1, 0, w1);
60          result.setElementAt(2, 0, w2);
61          result.setElementAt(3, 0, w3);
62  
63          result.setElementAt(0, 1, -w1);
64          result.setElementAt(1, 1, 0.0);
65          result.setElementAt(2, 1, -w3);
66          result.setElementAt(3, 1, w2);
67  
68          result.setElementAt(0, 2, -w2);
69          result.setElementAt(1, 2, w3);
70          result.setElementAt(2, 2, 0.0);
71          result.setElementAt(3, 2, -w1);
72  
73          result.setElementAt(0, 3, -w3);
74          result.setElementAt(1, 3, -w2);
75          result.setElementAt(2, 3, w1);
76          result.setElementAt(3, 3, 0.0);
77      }
78  
79      /**
80       * Skew symmetric matrix omega from angular rates vector w.
81       *
82       * @param w      array containing angular rates. Must have length 3.
83       * @param result instance where result will be stored.
84       * @throws IllegalArgumentException if provided matrix is not 4x4 or array w
85       *                                  does not have length 3.
86       * @see <a href="https://github.com/joansola/slamtb">w2omega.m at https://github.com/joansola/slamtb</a>
87       */
88      public static void w2omega(final double[] w, final Matrix result) {
89          if (w.length != N_ANGULAR_RATES) {
90              throw new IllegalArgumentException("w must have length 3");
91          }
92          w2omega(w[0], w[1], w[2], result);
93      }
94  
95      /**
96       * Skew symmetric matrix omega from angular rates vector w.
97       *
98       * @param w1 angular rate from x-axis.
99       * @param w2 angular rate from y-axis.
100      * @param w3 angular rate from z-axis.
101      * @return a new skew symmetric matrix omega.
102      * @see <a href="https://github.com/joansola/slamtb">w2omega.m at https://github.com/joansola/slamtb</a>
103      */
104     public static Matrix w2omega(final double w1, final double w2, final double w3) {
105         Matrix result = null;
106         try {
107             result = new Matrix(SKEW_MATRIX_SIZE, SKEW_MATRIX_SIZE);
108             w2omega(w1, w2, w3, result);
109         } catch (final WrongSizeException ignore) {
110             // never happens
111         }
112 
113         return result;
114     }
115 
116     /**
117      * Skew symmetric matrix omega from angular rates vector w.
118      *
119      * @param w array containing angular rates. Must have length 3.
120      * @return a new skew symmetric matrix omega.
121      * @see <a href="https://github.com/joansola/slamtb">w2omega.m at https://github.com/joansola/slamtb</a>
122      */
123     public static Matrix w2omega(final double[] w) {
124         if (w.length != N_ANGULAR_RATES) {
125             throw new IllegalArgumentException("w must have length 3");
126         }
127         return w2omega(w[0], w[1], w[2]);
128     }
129 
130     /**
131      * Converts provided quaternion into Pi matrix.
132      * Given a quaternion q = [a b c d]', and the angular rates vector
133      * w = [p q r]' and omega = w2omega(w) a skew symmetric matrix, then the PI
134      * matrix is the Jacobian respect to w, expressed as: PI = omega(w)*q.
135      *
136      * @param quaternion a quaternion.
137      * @param result     matrix where resulting pi matrix is stored.
138      * @throws IllegalArgumentException if provided result matrix is not 4x3.
139      * @see <a href="https://github.com/joansola/slamtb">q2Pi.m at https://github.com/joansola/slamtb</a>
140      */
141     public static void quaternionToPiMatrix(final Quaternion quaternion, final Matrix result) {
142         if (result.getRows() != Quaternion.N_PARAMS || result.getColumns() != Quaternion.N_ANGLES) {
143             throw new IllegalArgumentException("result matrix must be 4x3");
144         }
145 
146         final var a = quaternion.getA();
147         final var b = quaternion.getB();
148         final var c = quaternion.getC();
149         final var d = quaternion.getD();
150 
151         result.setElementAt(0, 0, -b);
152         result.setElementAt(1, 0, a);
153         result.setElementAt(2, 0, d);
154         result.setElementAt(3, 0, -c);
155 
156         result.setElementAt(0, 1, -c);
157         result.setElementAt(1, 1, -d);
158         result.setElementAt(2, 1, a);
159         result.setElementAt(3, 1, b);
160 
161         result.setElementAt(0, 2, -d);
162         result.setElementAt(1, 2, c);
163         result.setElementAt(2, 2, -b);
164         result.setElementAt(3, 2, a);
165     }
166 
167     /**
168      * Converts provided quaternion into Pi matrix.
169      * Given a quaternion q = [a b c d]', and the angular rates vector
170      * w = [p q r]' and omega = w2omega(w) a skew symmetric matrix, then the PI
171      * matrix is the Jacobian respect to w, expressed as: PI = omega(w)*q.
172      *
173      * @param quaternion a quaternion.
174      * @return pi matrix.
175      * @see <a href="https://github.com/joansola/slamtb">q2Pi.m at https://github.com/joansola/slamtb</a>
176      */
177     public static Matrix quaternionToPiMatrix(final Quaternion quaternion) {
178         Matrix m = null;
179         try {
180             m = new Matrix(Quaternion.N_PARAMS, Quaternion.N_ANGLES);
181             quaternionToPiMatrix(quaternion, m);
182         } catch (final WrongSizeException ignore) {
183             // never thrown
184         }
185         return m;
186     }
187 
188     /**
189      * Converts provided quaternion into conjugated Pi matrix.
190      * Given a quaternion q = [a b c d]', then the conjugated Pi matrix is the
191      * Pi matrix of the conjugated quaternion [a -b -c -d]'.
192      *
193      * @param quaternion a quaternion.
194      * @param result     matrix where resulting conjugated pi matrix is stored.
195      * @throws IllegalArgumentException if provided result matrix is not 4x3.
196      * @see <a href="https://github.com/joansola/slamtb">pi2pc.m at https://github.com/joansola/slamtb</a>
197      */
198     public static void quaternionToConjugatedPiMatrix(final Quaternion quaternion, final Matrix result) {
199         quaternionToPiMatrix(quaternion.conjugateAndReturnNew(), result);
200     }
201 
202     /**
203      * Converts provided quaternion into conjugated Pi matrix.
204      * Given a quaternion q = [a b c d]', then the conjugated Pi matrix is the
205      * Pi matrix of the conjugated quaternion [a -b -c -d]'.
206      *
207      * @param quaternion a quaternion.
208      * @return a matrix containing the conjugated pi matrix.
209      * @see <a href="https://github.com/joansola/slamtb">pi2pc.m at https://github.com/joansola/slamtb</a>
210      */
211     public static Matrix quaternionToConjugatedPiMatrix(final Quaternion quaternion) {
212         Matrix m = null;
213         try {
214             m = new Matrix(Quaternion.N_PARAMS, Quaternion.N_ANGLES);
215             quaternionToConjugatedPiMatrix(quaternion, m);
216         } catch (final WrongSizeException ignore) {
217             // never thrown
218         }
219         return m;
220     }
221 
222     /**
223      * Computes conjugated pi matrix from pi matrix.
224      *
225      * @param pi     pi matrix used as input.
226      * @param result instance where conjugated pi matrix is stored.
227      * @throws IllegalArgumentException if provided matrices are not 4x3.
228      * @see <a href="https://github.com/joansola/slamtb">pi2pc.m at https://github.com/joansola/slamtb</a>
229      */
230     public static void piMatrixToConjugatedPiMatrix(final Matrix pi, final Matrix result) {
231         if (pi.getRows() != Quaternion.N_PARAMS || pi.getColumns() != Quaternion.N_ANGLES) {
232             throw new IllegalArgumentException("pi must be 4x3");
233         }
234         if (result.getRows() != Quaternion.N_PARAMS || result.getColumns() != Quaternion.N_ANGLES) {
235             throw new IllegalArgumentException("result must be 4x3");
236         }
237 
238         result.copyFrom(pi);
239 
240         result.setElementAtIndex(0, -pi.getElementAtIndex(0));
241         result.setElementAtIndex(2, -pi.getElementAtIndex(2));
242         result.setElementAtIndex(3, -pi.getElementAtIndex(3));
243         result.setElementAtIndex(4, -pi.getElementAtIndex(4));
244         result.setElementAtIndex(5, -pi.getElementAtIndex(5));
245         result.setElementAtIndex(7, -pi.getElementAtIndex(7));
246         result.setElementAtIndex(8, -pi.getElementAtIndex(8));
247         result.setElementAtIndex(9, -pi.getElementAtIndex(9));
248         result.setElementAtIndex(10, -pi.getElementAtIndex(10));
249     }
250 
251     /**
252      * Computes conjugated pi matrix from pi matrix.
253      *
254      * @param pi pi matrix used as input.
255      * @return conjugated pi matrix.
256      * @throws IllegalArgumentException if provided input matrix is not 4x3.
257      * @see <a href="https://github.com/joansola/slamtb">pi2pc.m at https://github.com/joansola/slamtb</a>
258      */
259     public static Matrix piMatrixToConjugatedPiMatrix(final Matrix pi) {
260         Matrix m = null;
261         try {
262             m = new Matrix(Quaternion.N_PARAMS, Quaternion.N_ANGLES);
263             piMatrixToConjugatedPiMatrix(pi, m);
264         } catch (final WrongSizeException ignore) {
265             // never thrown
266         }
267         return m;
268     }
269 
270     /**
271      * Rotates a point by using the rotation matrix obtained from a quaternion.
272      *
273      * @param q         quaternion containing rotation information.
274      * @param point     array containing inhomogeneous 3D coordinates of a point to
275      *                  be rotated.
276      * @param result    array containing result of rotation.
277      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
278      * @param jacobianP jacobian wrt of point. Must be 3x3.
279      * @throws IllegalArgumentException if provided arrays of points or result
280      *                                  don't have length 3, or if jacobian of quaternions is not 3x4 (if
281      *                                  provided), or if jacobian of point is not 3x3 (if provided).
282      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
283      */
284     public static void rotationMatrixTimesVector(
285             final Quaternion q, final double[] point, final double[] result, final Matrix jacobianQ,
286             final Matrix jacobianP) {
287         if (point.length != Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH) {
288             throw new IllegalArgumentException("point must have length 3");
289         }
290         if (result.length != Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH) {
291             throw new IllegalArgumentException("result must have length 3");
292         }
293         if (jacobianQ != null && (jacobianQ.getRows() != Quaternion.N_ANGLES
294                 || jacobianQ.getColumns() != Quaternion.N_PARAMS)) {
295             throw new IllegalArgumentException("jacobian wrt of quaternion must be 3x4");
296         }
297         if (jacobianP != null && (jacobianP.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
298                 || jacobianP.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS)) {
299             throw new IllegalArgumentException("jacobian wrt of point must be 3x3");
300         }
301 
302         try {
303             final var r = new Matrix(MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS,
304                     MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS);
305             q.toMatrixRotation(r);
306 
307             if (jacobianP != null) {
308                 jacobianP.copyFrom(r);
309             }
310             // multiply rotation matrix by point
311             final var p = Matrix.newFromArray(point, true);
312             r.multiply(p);
313 
314             // copy to result
315             r.getSubmatrixAsArray(0, 0, r.getRows() - 1,
316                     r.getColumns() - 1, result);
317 
318             if (jacobianQ != null) {
319                 final var a = q.getA();
320                 final var b = q.getB();
321                 final var c = q.getC();
322                 final var d = q.getD();
323 
324                 final var x = point[0];
325                 final var y = point[1];
326                 final var z = point[2];
327 
328                 final var axdycz = 2.0 * (a * x - d * y + c * z);
329                 final var bxcydz = 2.0 * (b * x + c * y + d * z);
330                 final var cxbyaz = 2.0 * (c * x - b * y - a * z);
331                 final var dxaybz = 2.0 * (d * x + a * y - b * z);
332 
333                 jacobianQ.setElementAt(0, 0, axdycz);
334                 jacobianQ.setElementAt(1, 0, dxaybz);
335                 jacobianQ.setElementAt(2, 0, -cxbyaz);
336 
337                 jacobianQ.setElementAt(0, 1, bxcydz);
338                 jacobianQ.setElementAt(1, 1, cxbyaz);
339                 jacobianQ.setElementAt(2, 1, dxaybz);
340 
341                 jacobianQ.setElementAt(0, 2, -cxbyaz);
342                 jacobianQ.setElementAt(1, 2, bxcydz);
343                 jacobianQ.setElementAt(2, 2, -axdycz);
344 
345                 jacobianQ.setElementAt(0, 3, -dxaybz);
346                 jacobianQ.setElementAt(1, 3, axdycz);
347                 jacobianQ.setElementAt(2, 3, bxcydz);
348             }
349         } catch (final WrongSizeException ignore) {
350             // never thrown
351         }
352     }
353 
354     /**
355      * Rotates a point by using the rotation matrix obtained from a quaternion.
356      *
357      * @param q         quaternion containing rotation information.
358      * @param point     array containing inhomogeneous 3D coordinates of a point to
359      *                  be rotated.
360      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
361      * @param jacobianP jacobian wrt of point. Must be 3x3.
362      * @return array containing result of rotation as 3D inhomogeneous
363      * coordinates.
364      * @throws IllegalArgumentException if provided point array doesn't have
365      *                                  length 3, or if jacobian of quaternions is not 3x4 (if provided), or if
366      *                                  jacobian of point is not 3x3 (if provided).
367      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
368      */
369     public static double[] rotationMatrixTimesVector(
370             final Quaternion q, final double[] point, final Matrix jacobianQ, final Matrix jacobianP) {
371         final var result = new double[Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH];
372         rotationMatrixTimesVector(q, point, result, jacobianQ, jacobianP);
373         return result;
374     }
375 
376     /**
377      * Rotates a point by using the rotation matrix obtained from a quaternion.
378      *
379      * @param q      quaternion containing rotation information.
380      * @param point  array containing inhomogeneous 3D coordinates of a point to
381      *               be rotated.
382      * @param result array containing result of rotation.
383      * @throws IllegalArgumentException if provided arrays of points or result
384      *                                  don't have length 3.
385      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
386      */
387     public static void rotationMatrixTimesVector(final Quaternion q, final double[] point, final double[] result) {
388         rotationMatrixTimesVector(q, point, result, null, null);
389     }
390 
391     /**
392      * Rotates a point by using the rotation matrix obtained from a quaternion.
393      *
394      * @param q     quaternion containing rotation information.
395      * @param point array containing inhomogeneous 3D coordinates of a point to
396      *              be rotated.
397      * @return array containing result of rotation as 3D inhomogeneous
398      * coordinates.
399      * @throws IllegalArgumentException if provided point array doesn't have
400      *                                  length 3.
401      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
402      */
403     public static double[] rotationMatrixTimesVector(final Quaternion q, final double[] point) {
404         return rotationMatrixTimesVector(q, point, null, null);
405     }
406 
407     /**
408      * Rotates a 3D point by using the rotation matrix obtained from a
409      * quaternion.
410      *
411      * @param q         quaternion containing rotation information.
412      * @param point     3D point to be rotated.
413      * @param result    result of rotation.
414      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
415      * @param jacobianP jacobian wrt of point. Must be 3x3.
416      * @throws IllegalArgumentException if jacobian of quaternions is not 3x4
417      *                                  (if provided), or if jacobian of point is not 3x3 (if provided).
418      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
419      */
420     public static void rotationMatrixTimesVector(
421             final Quaternion q, final Point3D point, final Point3D result, final Matrix jacobianQ,
422             final Matrix jacobianP) {
423         final var coords = new double[]{point.getInhomX(), point.getInhomY(), point.getInhomZ()};
424         final var rp = rotationMatrixTimesVector(q, coords, jacobianQ, jacobianP);
425         result.setInhomogeneousCoordinates(rp[0], rp[1], rp[2]);
426     }
427 
428     /**
429      * Rotates a 3D point by using the rotation matrix obtained from a
430      * quaternion.
431      *
432      * @param q         quaternion containing rotation information.
433      * @param point     3D point to be rotated.
434      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
435      * @param jacobianP jacobian wrt of point. Must be 3x3.
436      * @return result of rotation.
437      * @throws IllegalArgumentException if jacobian of quaternions is not 3x4.
438      *                                  (if provided), of if jacobian of point is not 3x3 (if provided).
439      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
440      */
441     public static Point3D rotationMatrixTimesVector(
442             final Quaternion q, final Point3D point, final Matrix jacobianQ, final Matrix jacobianP) {
443         final var result = Point3D.create();
444         rotationMatrixTimesVector(q, point, result, jacobianQ, jacobianP);
445         return result;
446     }
447 
448     /**
449      * Rotates a 3D point by using the rotation matrix obtained from a
450      * quaternion.
451      *
452      * @param q      quaternion containing rotation information.
453      * @param point  3D point to be rotated.
454      * @param result result of rotation.
455      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
456      */
457     public static void rotationMatrixTimesVector(final Quaternion q, final Point3D point, final Point3D result) {
458         rotationMatrixTimesVector(q, point, result, null, null);
459     }
460 
461     /**
462      * Rotates a 3D point by using the rotation matrix obtained from a
463      * quaternion.
464      *
465      * @param q     quaternion containing rotation information.
466      * @param point 3D point to be rotated.
467      * @return result of rotation.
468      * @see <a href="https://github.com/joansola/slamtb">Rp.m at https://github.com/joansola/slamtb</a>
469      */
470     public static Point3D rotationMatrixTimesVector(final Quaternion q, final Point3D point) {
471         return rotationMatrixTimesVector(q, point, null, null);
472     }
473 
474     /**
475      * Rotates a point by the inverse rotation obtained from a quaternion.
476      *
477      * @param q         quaternion containing rotation information.
478      * @param point     array containing inhomogeneous 3D coordinates of a point to
479      *                  be rotated.
480      * @param result    array containing result of rotation.
481      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
482      * @param jacobianP jacobian wrt of point. Must be 3x3.
483      * @throws IllegalArgumentException if provided arrays of points or result
484      *                                  don't have length 3, or if jacobian of quaternions is not 3x4 (if
485      *                                  provided), or if jacobian of point is not 3x3 (if provided).
486      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
487      */
488     public static void transposedRotationMatrixTimesVector(
489             final Quaternion q, final double[] point, final double[] result, final Matrix jacobianQ,
490             final Matrix jacobianP) {
491         if (point.length != Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH) {
492             throw new IllegalArgumentException("point must have length 3");
493         }
494         if (result.length != Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH) {
495             throw new IllegalArgumentException("result must have length 3");
496         }
497         if (jacobianQ != null && (jacobianQ.getRows() != Quaternion.N_ANGLES
498                 || jacobianQ.getColumns() != Quaternion.N_PARAMS)) {
499             throw new IllegalArgumentException("jacobian wrt of quaternion must be 3x4");
500         }
501         if (jacobianP != null && (jacobianP.getRows() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS
502                 || jacobianP.getColumns() != MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS)) {
503             throw new IllegalArgumentException("jacobian wrt of point must be 3x3");
504         }
505 
506         try {
507             final var rt = new Matrix(MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS,
508                     MatrixRotation3D.ROTATION3D_INHOM_MATRIX_COLS);
509             q.toMatrixRotation(rt);
510             // transpose rotation
511             // the transpose of rotation is its inverse
512             rt.transpose();
513 
514             if (jacobianP != null) {
515                 jacobianP.copyFrom(rt);
516             }
517             // multiply rotation matrix by point
518             final var p = Matrix.newFromArray(point, true);
519             rt.multiply(p);
520 
521             // copy to result
522             rt.getSubmatrixAsArray(0, 0, rt.getRows() - 1,
523                     rt.getColumns() - 1, result);
524 
525             if (jacobianQ != null) {
526                 final var a = q.getA();
527                 final var b = q.getB();
528                 final var c = q.getC();
529                 final var d = q.getD();
530 
531                 final var x = p.getElementAtIndex(0);
532                 final var y = p.getElementAtIndex(1);
533                 final var z = p.getElementAtIndex(2);
534 
535                 final var axdycz = 2.0 * (a * x + d * y - c * z);
536                 final var bxcydz = 2.0 * (b * x + c * y + d * z);
537                 final var cxbyaz = 2.0 * (c * x - b * y + a * z);
538                 final var dxaybz = 2.0 * (d * x - a * y - b * z);
539 
540                 jacobianQ.setElementAt(0, 0, axdycz);
541                 jacobianQ.setElementAt(1, 0, -dxaybz);
542                 jacobianQ.setElementAt(2, 0, cxbyaz);
543 
544                 jacobianQ.setElementAt(0, 1, bxcydz);
545                 jacobianQ.setElementAt(1, 1, cxbyaz);
546                 jacobianQ.setElementAt(2, 1, dxaybz);
547 
548                 jacobianQ.setElementAt(0, 2, -cxbyaz);
549                 jacobianQ.setElementAt(1, 2, bxcydz);
550                 jacobianQ.setElementAt(2, 2, axdycz);
551 
552                 jacobianQ.setElementAt(0, 3, -dxaybz);
553                 jacobianQ.setElementAt(1, 3, -axdycz);
554                 jacobianQ.setElementAt(2, 3, bxcydz);
555             }
556         } catch (final WrongSizeException ignore) {
557             // never thrown
558         }
559     }
560 
561     /**
562      * Rotates a point by the inverse rotation obtained from a quaternion.
563      *
564      * @param q         quaternion containing rotation information.
565      * @param point     array containing inhomogeneous 3D coordinates of a point to
566      *                  be rotated.
567      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
568      * @param jacobianP jacobian wrt of point. Must be 3x3.
569      * @return result of rotation.
570      * @throws IllegalArgumentException if provided arrays of points doesn't
571      *                                  have length 3, or if jacobian of quaternions is not 3x4 (if provided), or
572      *                                  if jacobian of point is not 3x3 (if provided).
573      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
574      */
575     public static double[] transposedRotationMatrixTimesVector(
576             final Quaternion q, final double[] point, final Matrix jacobianQ, final Matrix jacobianP) {
577         final var result = new double[Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH];
578         transposedRotationMatrixTimesVector(q, point, result, jacobianQ, jacobianP);
579         return result;
580     }
581 
582     /**
583      * Rotates a point by the inverse rotation obtained from a quaternion.
584      *
585      * @param q      quaternion containing rotation information.
586      * @param point  array containing inhomogeneous 3D coordinates of a point to
587      *               be rotated.
588      * @param result result of rotation.
589      * @throws IllegalArgumentException if provided arrays of points doesn't
590      *                                  have length 3.
591      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
592      */
593     public static void transposedRotationMatrixTimesVector(
594             final Quaternion q, final double[] point, final double[] result) {
595         transposedRotationMatrixTimesVector(q, point, result, null, null);
596     }
597 
598     /**
599      * Rotates a point by the inverse rotation obtained from a quaternion.
600      *
601      * @param q     quaternion containing rotation information.
602      * @param point array containing inhomogeneous 3D coordinates of a point to
603      *              be rotated.
604      * @return array containing result of rotation as 3D inhomogeneous
605      * coordinates.
606      * @throws IllegalArgumentException if provided point array doesn't have
607      *                                  length 3.
608      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
609      */
610     public static double[] transposedRotationMatrixTimesVector(final Quaternion q, final double[] point) {
611         return transposedRotationMatrixTimesVector(q, point, null, null);
612     }
613 
614     /**
615      * Rotates a 3D point by the inverse rotation obtained from a quaternion.
616      *
617      * @param q         quaternion containing rotation information.
618      * @param point     array containing inhomogeneous 3D coordinates of a point to
619      *                  be rotated.
620      * @param result    result of rotation.
621      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
622      * @param jacobianP jacobian wrt of point. Must be 3x3.
623      * @throws IllegalArgumentException if jacobian of quaternions is not 3x4
624      *                                  (if provided), or if jacobian of point is not 3x3 (if provided).
625      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
626      */
627     public static void transposedRotationMatrixTimesVector(
628             final Quaternion q, final Point3D point, final Point3D result, final Matrix jacobianQ,
629             final Matrix jacobianP) {
630         final var coords = new double[]{point.getInhomX(), point.getInhomY(), point.getInhomZ()};
631         final var rp = transposedRotationMatrixTimesVector(q, coords, jacobianQ, jacobianP);
632         result.setInhomogeneousCoordinates(rp[0], rp[1], rp[2]);
633     }
634 
635     /**
636      * Rotates a 3D point by the inverse rotation obtained from a quaternion.
637      *
638      * @param q         quaternion containing rotation information.
639      * @param point     array containing inhomogeneous 3D coordinates of a point to
640      *                  be rotated.
641      * @param jacobianQ jacobian wrt of quaternion. Must be 3x4.
642      * @param jacobianP jacobian wrt of point. Must be 3x3.
643      * @return result of rotation.
644      * @throws IllegalArgumentException if jacobian of quaternions is not 3x4
645      *                                  (if provided), or if jacobian of point is not 3x3 (if provided).
646      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
647      */
648     public static Point3D transposedRotationMatrixTimesVector(
649             final Quaternion q, final Point3D point, final Matrix jacobianQ, final Matrix jacobianP) {
650         final var result = Point3D.create();
651         transposedRotationMatrixTimesVector(q, point, result, jacobianQ, jacobianP);
652         return result;
653     }
654 
655     /**
656      * Rotates a 3D point by the inverse rotation obtained from a quaternion.
657      *
658      * @param q      quaternion containing rotation information.
659      * @param point  3D point to be rotated.
660      * @param result result of rotation.
661      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
662      */
663     public static void transposedRotationMatrixTimesVector(
664             final Quaternion q, final Point3D point, final Point3D result) {
665         transposedRotationMatrixTimesVector(q, point, result, null, null);
666     }
667 
668     /**
669      * Rotates a 3D point by the inverse rotation obtained from a quaternion.
670      *
671      * @param q     quaternion containing rotation information.
672      * @param point 3D point to be rotated.
673      * @return result of rotation.
674      * @see <a href="https://github.com/joansola/slamtb">Rtp.m at https://github.com/joansola/slamtb</a>
675      */
676     public static Point3D transposedRotationMatrixTimesVector(final Quaternion q, final Point3D point) {
677         return transposedRotationMatrixTimesVector(q, point, null, null);
678     }
679 
680     /**
681      * Rotation to convert camera body to camera sensor.
682      *
683      * @param result instance where resulting rotation will be stored.
684      */
685     public static void cameraBodyToCameraSensorRotation(final MatrixRotation3D result) {
686         Quaternion.eulerToMatrixRotation(-Math.PI / 2.0, 0, -Math.PI / 2.0, result);
687     }
688 
689     /**
690      * Rotation to convert camera body to camera sensor.
691      *
692      * @return a new instance containing rotation.
693      */
694     public static MatrixRotation3D cameraBodyToCameraSensorRotation() {
695         final var result = new MatrixRotation3D();
696         cameraBodyToCameraSensorRotation(result);
697         return result;
698     }
699 
700     /**
701      * Transform quaternion Gaussian to Euler Gaussian.
702      * The Gaussian quaternion is provided as N(q, Q) where q is the quaternion
703      * mean and Q is the quaternion covariance, and returns an Euler angles
704      * Gaussian as N(e,E) where e is the Euler angles mean and E is the Euler
705      * angles covariance.
706      *
707      * @param q                    mean quaternion to be transformed.
708      * @param quaternionCovariance quaternion covariance to be transformed.
709      * @param angles               obtained mean Euler angles.
710      * @param anglesCovariance     obtained Euler covariance.
711      * @throws IllegalArgumentException if provided quaternion covariance is
712      *                                  not 4x4
713      * @see <a href="https://github.com/joansola/slamtb">q2eG.m at https://github.com/joansola/slamtb</a>
714      */
715     public static void quaternionToEulerGaussian(
716             final Quaternion q, final Matrix quaternionCovariance, final double[] angles,
717             final Matrix anglesCovariance) {
718         if (quaternionCovariance.getRows() != Quaternion.N_PARAMS
719                 || quaternionCovariance.getColumns() != Quaternion.N_PARAMS) {
720             throw new IllegalArgumentException("quaternion covariance must be 4x4");
721         }
722 
723         try {
724             final var eq = new Matrix(Quaternion.N_ANGLES, Quaternion.N_PARAMS);
725             q.toEulerAngles(angles, eq);
726 
727             // jacobian = eq * Q * eq', where is provided covariance
728             anglesCovariance.copyFrom(eq);
729             anglesCovariance.multiply(quaternionCovariance);
730             anglesCovariance.multiply(eq.transposeAndReturnNew());
731         } catch (final WrongSizeException ignore) {
732             // never thrown
733         }
734     }
735 
736     /**
737      * Obtains the skew symmetric matrix from angular rates vector.
738      *
739      * @param angularRates a vector containing the 3 components (x,y,z) of
740      *                     angular rates (rad/s).
741      * @param result       a skew symmetric matrix.
742      * @throws IllegalArgumentException if provided array of angular rates does
743      *                                  not have length 3 or if provided result matrix is not 4x4.
744      */
745     public static void angularRatesToSkew(final double[] angularRates, final Matrix result) {
746         if (angularRates.length != Quaternion.N_ANGLES) {
747             throw new IllegalArgumentException("angular rates must have length 3");
748         }
749         if (result.getRows() != Quaternion.N_PARAMS || result.getColumns() != Quaternion.N_PARAMS) {
750             throw new IllegalArgumentException("result matrix must be 4x4");
751         }
752 
753         result.setElementAt(0, 0, 0.0);
754         result.setElementAt(1, 0, angularRates[0]);
755         result.setElementAt(2, 0, angularRates[1]);
756         result.setElementAt(3, 0, angularRates[2]);
757 
758         result.setElementAt(0, 1, -angularRates[0]);
759         result.setElementAt(1, 1, 0.0);
760         result.setElementAt(2, 1, -angularRates[2]);
761         result.setElementAt(3, 1, angularRates[1]);
762 
763         result.setElementAt(0, 2, -angularRates[1]);
764         result.setElementAt(1, 2, angularRates[2]);
765         result.setElementAt(2, 2, 0.0);
766         result.setElementAt(3, 2, -angularRates[0]);
767 
768         result.setElementAt(0, 3, -angularRates[2]);
769         result.setElementAt(1, 3, -angularRates[1]);
770         result.setElementAt(2, 3, angularRates[0]);
771         result.setElementAt(3, 3, 0.0);
772     }
773 
774     /**
775      * Obtains the skew symmetric matrix from angular rates vector.
776      *
777      * @param angularRates a vector containing the 3 components (x,y,z) of
778      *                     angular rates (rad/s).
779      * @return a new instance containing the skew symmetric matrix
780      * @throws IllegalArgumentException if provided array of angular rates does
781      *                                  not have length 3.
782      */
783     public static Matrix angularRatesToSkew(final double[] angularRates) {
784         Matrix result = null;
785         try {
786             result = new Matrix(Quaternion.N_PARAMS, Quaternion.N_PARAMS);
787             angularRatesToSkew(angularRates, result);
788         } catch (final WrongSizeException ignore) {
789             // never thrown
790         }
791         return result;
792     }
793 
794 }