1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package org.apache.commons.math3.geometry.euclidean.threed;
19 
20 import java.io.Serializable;
21 
22 import org.apache.commons.math3.RealFieldElement;
23 import org.apache.commons.math3.Field;
24 import org.apache.commons.math3.exception.MathArithmeticException;
25 import org.apache.commons.math3.exception.MathIllegalArgumentException;
26 import org.apache.commons.math3.exception.util.LocalizedFormats;
27 import org.apache.commons.math3.util.FastMath;
28 import org.apache.commons.math3.util.MathArrays;
29 
30 /**
31  * This class is a re-implementation of {@link Rotation} using {@link RealFieldElement}.
32  * <p>Instance of this class are guaranteed to be immutable.</p>
33  *
34  * @param <T> the type of the field elements
35  * @see FieldVector3D
36  * @see RotationOrder
37  * @since 3.2
38  */
39 
40 public class FieldRotation<T extends RealFieldElement<T>> implements Serializable {
41 
42     /** Serializable version identifier */
43     private static final long serialVersionUID = 20130224l;
44 
45     /** Scalar coordinate of the quaternion. */
46     private final T q0;
47 
48     /** First coordinate of the vectorial part of the quaternion. */
49     private final T q1;
50 
51     /** Second coordinate of the vectorial part of the quaternion. */
52     private final T q2;
53 
54     /** Third coordinate of the vectorial part of the quaternion. */
55     private final T q3;
56 
57     /** Build a rotation from the quaternion coordinates.
58      * <p>A rotation can be built from a <em>normalized</em> quaternion,
59      * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> +
60      * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> +
61      * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized,
62      * the constructor can normalize it in a preprocessing step.</p>
63      * <p>Note that some conventions put the scalar part of the quaternion
64      * as the 4<sup>th</sup> component and the vector part as the first three
65      * components. This is <em>not</em> our convention. We put the scalar part
66      * as the first component.</p>
67      * @param q0 scalar part of the quaternion
68      * @param q1 first coordinate of the vectorial part of the quaternion
69      * @param q2 second coordinate of the vectorial part of the quaternion
70      * @param q3 third coordinate of the vectorial part of the quaternion
71      * @param needsNormalization if true, the coordinates are considered
72      * not to be normalized, a normalization preprocessing step is performed
73      * before using them
74      */
FieldRotation(final T q0, final T q1, final T q2, final T q3, final boolean needsNormalization)75     public FieldRotation(final T q0, final T q1, final T q2, final T q3, final boolean needsNormalization) {
76 
77         if (needsNormalization) {
78             // normalization preprocessing
79             final T inv =
80                     q0.multiply(q0).add(q1.multiply(q1)).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().reciprocal();
81             this.q0 = inv.multiply(q0);
82             this.q1 = inv.multiply(q1);
83             this.q2 = inv.multiply(q2);
84             this.q3 = inv.multiply(q3);
85         } else {
86             this.q0 = q0;
87             this.q1 = q1;
88             this.q2 = q2;
89             this.q3 = q3;
90         }
91 
92     }
93 
94     /** Build a rotation from an axis and an angle.
95      * <p>We use the convention that angles are oriented according to
96      * the effect of the rotation on vectors around the axis. That means
97      * that if (i, j, k) is a direct frame and if we first provide +k as
98      * the axis and &pi;/2 as the angle to this constructor, and then
99      * {@link #applyTo(FieldVector3D) apply} the instance to +i, we will get
100      * +j.</p>
101      * <p>Another way to represent our convention is to say that a rotation
102      * of angle &theta; about the unit vector (x, y, z) is the same as the
103      * rotation build from quaternion components { cos(-&theta;/2),
104      * x * sin(-&theta;/2), y * sin(-&theta;/2), z * sin(-&theta;/2) }.
105      * Note the minus sign on the angle!</p>
106      * <p>On the one hand this convention is consistent with a vectorial
107      * perspective (moving vectors in fixed frames), on the other hand it
108      * is different from conventions with a frame perspective (fixed vectors
109      * viewed from different frames) like the ones used for example in spacecraft
110      * attitude community or in the graphics community.</p>
111      * @param axis axis around which to rotate
112      * @param angle rotation angle.
113      * @exception MathIllegalArgumentException if the axis norm is zero
114      * @deprecated as of 3.6, replaced with {@link
115      * #FieldRotation(FieldVector3D, RealFieldElement, RotationConvention)}
116      */
117     @Deprecated
FieldRotation(final FieldVector3D<T> axis, final T angle)118     public FieldRotation(final FieldVector3D<T> axis, final T angle)
119         throws MathIllegalArgumentException {
120         this(axis, angle, RotationConvention.VECTOR_OPERATOR);
121     }
122 
123     /** Build a rotation from an axis and an angle.
124      * <p>We use the convention that angles are oriented according to
125      * the effect of the rotation on vectors around the axis. That means
126      * that if (i, j, k) is a direct frame and if we first provide +k as
127      * the axis and &pi;/2 as the angle to this constructor, and then
128      * {@link #applyTo(FieldVector3D) apply} the instance to +i, we will get
129      * +j.</p>
130      * <p>Another way to represent our convention is to say that a rotation
131      * of angle &theta; about the unit vector (x, y, z) is the same as the
132      * rotation build from quaternion components { cos(-&theta;/2),
133      * x * sin(-&theta;/2), y * sin(-&theta;/2), z * sin(-&theta;/2) }.
134      * Note the minus sign on the angle!</p>
135      * <p>On the one hand this convention is consistent with a vectorial
136      * perspective (moving vectors in fixed frames), on the other hand it
137      * is different from conventions with a frame perspective (fixed vectors
138      * viewed from different frames) like the ones used for example in spacecraft
139      * attitude community or in the graphics community.</p>
140      * @param axis axis around which to rotate
141      * @param angle rotation angle.
142      * @param convention convention to use for the semantics of the angle
143      * @exception MathIllegalArgumentException if the axis norm is zero
144      * @since 3.6
145      */
FieldRotation(final FieldVector3D<T> axis, final T angle, final RotationConvention convention)146     public FieldRotation(final FieldVector3D<T> axis, final T angle, final RotationConvention convention)
147         throws MathIllegalArgumentException {
148 
149         final T norm = axis.getNorm();
150         if (norm.getReal() == 0) {
151             throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS);
152         }
153 
154         final T halfAngle = angle.multiply(convention == RotationConvention.VECTOR_OPERATOR ? -0.5 : 0.5);
155         final T coeff = halfAngle.sin().divide(norm);
156 
157         q0 = halfAngle.cos();
158         q1 = coeff.multiply(axis.getX());
159         q2 = coeff.multiply(axis.getY());
160         q3 = coeff.multiply(axis.getZ());
161 
162     }
163 
164     /** Build a rotation from a 3X3 matrix.
165 
166      * <p>Rotation matrices are orthogonal matrices, i.e. unit matrices
167      * (which are matrices for which m.m<sup>T</sup> = I) with real
168      * coefficients. The module of the determinant of unit matrices is
169      * 1, among the orthogonal 3X3 matrices, only the ones having a
170      * positive determinant (+1) are rotation matrices.</p>
171 
172      * <p>When a rotation is defined by a matrix with truncated values
173      * (typically when it is extracted from a technical sheet where only
174      * four to five significant digits are available), the matrix is not
175      * orthogonal anymore. This constructor handles this case
176      * transparently by using a copy of the given matrix and applying a
177      * correction to the copy in order to perfect its orthogonality. If
178      * the Frobenius norm of the correction needed is above the given
179      * threshold, then the matrix is considered to be too far from a
180      * true rotation matrix and an exception is thrown.<p>
181 
182      * @param m rotation matrix
183      * @param threshold convergence threshold for the iterative
184      * orthogonality correction (convergence is reached when the
185      * difference between two steps of the Frobenius norm of the
186      * correction is below this threshold)
187 
188      * @exception NotARotationMatrixException if the matrix is not a 3X3
189      * matrix, or if it cannot be transformed into an orthogonal matrix
190      * with the given threshold, or if the determinant of the resulting
191      * orthogonal matrix is negative
192 
193      */
FieldRotation(final T[][] m, final double threshold)194     public FieldRotation(final T[][] m, final double threshold)
195         throws NotARotationMatrixException {
196 
197         // dimension check
198         if ((m.length != 3) || (m[0].length != 3) ||
199                 (m[1].length != 3) || (m[2].length != 3)) {
200             throw new NotARotationMatrixException(
201                                                   LocalizedFormats.ROTATION_MATRIX_DIMENSIONS,
202                                                   m.length, m[0].length);
203         }
204 
205         // compute a "close" orthogonal matrix
206         final T[][] ort = orthogonalizeMatrix(m, threshold);
207 
208         // check the sign of the determinant
209         final T d0 = ort[1][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[1][2]));
210         final T d1 = ort[0][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[0][2]));
211         final T d2 = ort[0][1].multiply(ort[1][2]).subtract(ort[1][1].multiply(ort[0][2]));
212         final T det =
213                 ort[0][0].multiply(d0).subtract(ort[1][0].multiply(d1)).add(ort[2][0].multiply(d2));
214         if (det.getReal() < 0.0) {
215             throw new NotARotationMatrixException(
216                                                   LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT,
217                                                   det);
218         }
219 
220         final T[] quat = mat2quat(ort);
221         q0 = quat[0];
222         q1 = quat[1];
223         q2 = quat[2];
224         q3 = quat[3];
225 
226     }
227 
228     /** Build the rotation that transforms a pair of vectors into another pair.
229 
230      * <p>Except for possible scale factors, if the instance were applied to
231      * the pair (u<sub>1</sub>, u<sub>2</sub>) it will produce the pair
232      * (v<sub>1</sub>, v<sub>2</sub>).</p>
233 
234      * <p>If the angular separation between u<sub>1</sub> and u<sub>2</sub> is
235      * not the same as the angular separation between v<sub>1</sub> and
236      * v<sub>2</sub>, then a corrected v'<sub>2</sub> will be used rather than
237      * v<sub>2</sub>, the corrected vector will be in the (&pm;v<sub>1</sub>,
238      * +v<sub>2</sub>) half-plane.</p>
239 
240      * @param u1 first vector of the origin pair
241      * @param u2 second vector of the origin pair
242      * @param v1 desired image of u1 by the rotation
243      * @param v2 desired image of u2 by the rotation
244      * @exception MathArithmeticException if the norm of one of the vectors is zero,
245      * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear)
246      */
FieldRotation(FieldVector3D<T> u1, FieldVector3D<T> u2, FieldVector3D<T> v1, FieldVector3D<T> v2)247     public FieldRotation(FieldVector3D<T> u1, FieldVector3D<T> u2, FieldVector3D<T> v1, FieldVector3D<T> v2)
248         throws MathArithmeticException {
249 
250         // build orthonormalized base from u1, u2
251         // this fails when vectors are null or collinear, which is forbidden to define a rotation
252         final FieldVector3D<T> u3 = FieldVector3D.crossProduct(u1, u2).normalize();
253         u2 = FieldVector3D.crossProduct(u3, u1).normalize();
254         u1 = u1.normalize();
255 
256         // build an orthonormalized base from v1, v2
257         // this fails when vectors are null or collinear, which is forbidden to define a rotation
258         final FieldVector3D<T> v3 = FieldVector3D.crossProduct(v1, v2).normalize();
259         v2 = FieldVector3D.crossProduct(v3, v1).normalize();
260         v1 = v1.normalize();
261 
262         // buid a matrix transforming the first base into the second one
263         final T[][] array = MathArrays.buildArray(u1.getX().getField(), 3, 3);
264         array[0][0] = u1.getX().multiply(v1.getX()).add(u2.getX().multiply(v2.getX())).add(u3.getX().multiply(v3.getX()));
265         array[0][1] = u1.getY().multiply(v1.getX()).add(u2.getY().multiply(v2.getX())).add(u3.getY().multiply(v3.getX()));
266         array[0][2] = u1.getZ().multiply(v1.getX()).add(u2.getZ().multiply(v2.getX())).add(u3.getZ().multiply(v3.getX()));
267         array[1][0] = u1.getX().multiply(v1.getY()).add(u2.getX().multiply(v2.getY())).add(u3.getX().multiply(v3.getY()));
268         array[1][1] = u1.getY().multiply(v1.getY()).add(u2.getY().multiply(v2.getY())).add(u3.getY().multiply(v3.getY()));
269         array[1][2] = u1.getZ().multiply(v1.getY()).add(u2.getZ().multiply(v2.getY())).add(u3.getZ().multiply(v3.getY()));
270         array[2][0] = u1.getX().multiply(v1.getZ()).add(u2.getX().multiply(v2.getZ())).add(u3.getX().multiply(v3.getZ()));
271         array[2][1] = u1.getY().multiply(v1.getZ()).add(u2.getY().multiply(v2.getZ())).add(u3.getY().multiply(v3.getZ()));
272         array[2][2] = u1.getZ().multiply(v1.getZ()).add(u2.getZ().multiply(v2.getZ())).add(u3.getZ().multiply(v3.getZ()));
273 
274         T[] quat = mat2quat(array);
275         q0 = quat[0];
276         q1 = quat[1];
277         q2 = quat[2];
278         q3 = quat[3];
279 
280     }
281 
282     /** Build one of the rotations that transform one vector into another one.
283 
284      * <p>Except for a possible scale factor, if the instance were
285      * applied to the vector u it will produce the vector v. There is an
286      * infinite number of such rotations, this constructor choose the
287      * one with the smallest associated angle (i.e. the one whose axis
288      * is orthogonal to the (u, v) plane). If u and v are collinear, an
289      * arbitrary rotation axis is chosen.</p>
290 
291      * @param u origin vector
292      * @param v desired image of u by the rotation
293      * @exception MathArithmeticException if the norm of one of the vectors is zero
294      */
FieldRotation(final FieldVector3D<T> u, final FieldVector3D<T> v)295     public FieldRotation(final FieldVector3D<T> u, final FieldVector3D<T> v) throws MathArithmeticException {
296 
297         final T normProduct = u.getNorm().multiply(v.getNorm());
298         if (normProduct.getReal() == 0) {
299             throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR);
300         }
301 
302         final T dot = FieldVector3D.dotProduct(u, v);
303 
304         if (dot.getReal() < ((2.0e-15 - 1.0) * normProduct.getReal())) {
305             // special case u = -v: we select a PI angle rotation around
306             // an arbitrary vector orthogonal to u
307             final FieldVector3D<T> w = u.orthogonal();
308             q0 = normProduct.getField().getZero();
309             q1 = w.getX().negate();
310             q2 = w.getY().negate();
311             q3 = w.getZ().negate();
312         } else {
313             // general case: (u, v) defines a plane, we select
314             // the shortest possible rotation: axis orthogonal to this plane
315             q0 = dot.divide(normProduct).add(1.0).multiply(0.5).sqrt();
316             final T coeff = q0.multiply(normProduct).multiply(2.0).reciprocal();
317             final FieldVector3D<T> q = FieldVector3D.crossProduct(v, u);
318             q1 = coeff.multiply(q.getX());
319             q2 = coeff.multiply(q.getY());
320             q3 = coeff.multiply(q.getZ());
321         }
322 
323     }
324 
325     /** Build a rotation from three Cardan or Euler elementary rotations.
326 
327      * <p>Cardan rotations are three successive rotations around the
328      * canonical axes X, Y and Z, each axis being used once. There are
329      * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler
330      * rotations are three successive rotations around the canonical
331      * axes X, Y and Z, the first and last rotations being around the
332      * same axis. There are 6 such sets of rotations (XYX, XZX, YXY,
333      * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p>
334      * <p>Beware that many people routinely use the term Euler angles even
335      * for what really are Cardan angles (this confusion is especially
336      * widespread in the aerospace business where Roll, Pitch and Yaw angles
337      * are often wrongly tagged as Euler angles).</p>
338 
339      * @param order order of rotations to use
340      * @param alpha1 angle of the first elementary rotation
341      * @param alpha2 angle of the second elementary rotation
342      * @param alpha3 angle of the third elementary rotation
343      * @deprecated as of 3.6, replaced with {@link
344      * #FieldRotation(RotationOrder, RotationConvention,
345      * RealFieldElement, RealFieldElement, RealFieldElement)}
346      */
347     @Deprecated
FieldRotation(final RotationOrder order, final T alpha1, final T alpha2, final T alpha3)348     public FieldRotation(final RotationOrder order, final T alpha1, final T alpha2, final T alpha3) {
349         this(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3);
350     }
351 
352     /** Build a rotation from three Cardan or Euler elementary rotations.
353 
354      * <p>Cardan rotations are three successive rotations around the
355      * canonical axes X, Y and Z, each axis being used once. There are
356      * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler
357      * rotations are three successive rotations around the canonical
358      * axes X, Y and Z, the first and last rotations being around the
359      * same axis. There are 6 such sets of rotations (XYX, XZX, YXY,
360      * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p>
361      * <p>Beware that many people routinely use the term Euler angles even
362      * for what really are Cardan angles (this confusion is especially
363      * widespread in the aerospace business where Roll, Pitch and Yaw angles
364      * are often wrongly tagged as Euler angles).</p>
365 
366      * @param order order of rotations to compose, from left to right
367      * (i.e. we will use {@code r1.compose(r2.compose(r3, convention), convention)})
368      * @param convention convention to use for the semantics of the angle
369      * @param alpha1 angle of the first elementary rotation
370      * @param alpha2 angle of the second elementary rotation
371      * @param alpha3 angle of the third elementary rotation
372      * @since 3.6
373      */
FieldRotation(final RotationOrder order, final RotationConvention convention, final T alpha1, final T alpha2, final T alpha3)374     public FieldRotation(final RotationOrder order, final RotationConvention convention,
375                          final T alpha1, final T alpha2, final T alpha3) {
376         final T one = alpha1.getField().getOne();
377         final FieldRotation<T> r1 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA1()), alpha1, convention);
378         final FieldRotation<T> r2 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA2()), alpha2, convention);
379         final FieldRotation<T> r3 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA3()), alpha3, convention);
380         final FieldRotation<T> composed = r1.compose(r2.compose(r3, convention), convention);
381         q0 = composed.q0;
382         q1 = composed.q1;
383         q2 = composed.q2;
384         q3 = composed.q3;
385     }
386 
387     /** Convert an orthogonal rotation matrix to a quaternion.
388      * @param ort orthogonal rotation matrix
389      * @return quaternion corresponding to the matrix
390      */
mat2quat(final T[][] ort)391     private T[] mat2quat(final T[][] ort) {
392 
393         final T[] quat = MathArrays.buildArray(ort[0][0].getField(), 4);
394 
395         // There are different ways to compute the quaternions elements
396         // from the matrix. They all involve computing one element from
397         // the diagonal of the matrix, and computing the three other ones
398         // using a formula involving a division by the first element,
399         // which unfortunately can be zero. Since the norm of the
400         // quaternion is 1, we know at least one element has an absolute
401         // value greater or equal to 0.5, so it is always possible to
402         // select the right formula and avoid division by zero and even
403         // numerical inaccuracy. Checking the elements in turn and using
404         // the first one greater than 0.45 is safe (this leads to a simple
405         // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19)
406         T s = ort[0][0].add(ort[1][1]).add(ort[2][2]);
407         if (s.getReal() > -0.19) {
408             // compute q0 and deduce q1, q2 and q3
409             quat[0] = s.add(1.0).sqrt().multiply(0.5);
410             T inv = quat[0].reciprocal().multiply(0.25);
411             quat[1] = inv.multiply(ort[1][2].subtract(ort[2][1]));
412             quat[2] = inv.multiply(ort[2][0].subtract(ort[0][2]));
413             quat[3] = inv.multiply(ort[0][1].subtract(ort[1][0]));
414         } else {
415             s = ort[0][0].subtract(ort[1][1]).subtract(ort[2][2]);
416             if (s.getReal() > -0.19) {
417                 // compute q1 and deduce q0, q2 and q3
418                 quat[1] = s.add(1.0).sqrt().multiply(0.5);
419                 T inv = quat[1].reciprocal().multiply(0.25);
420                 quat[0] = inv.multiply(ort[1][2].subtract(ort[2][1]));
421                 quat[2] = inv.multiply(ort[0][1].add(ort[1][0]));
422                 quat[3] = inv.multiply(ort[0][2].add(ort[2][0]));
423             } else {
424                 s = ort[1][1].subtract(ort[0][0]).subtract(ort[2][2]);
425                 if (s.getReal() > -0.19) {
426                     // compute q2 and deduce q0, q1 and q3
427                     quat[2] = s.add(1.0).sqrt().multiply(0.5);
428                     T inv = quat[2].reciprocal().multiply(0.25);
429                     quat[0] = inv.multiply(ort[2][0].subtract(ort[0][2]));
430                     quat[1] = inv.multiply(ort[0][1].add(ort[1][0]));
431                     quat[3] = inv.multiply(ort[2][1].add(ort[1][2]));
432                 } else {
433                     // compute q3 and deduce q0, q1 and q2
434                     s = ort[2][2].subtract(ort[0][0]).subtract(ort[1][1]);
435                     quat[3] = s.add(1.0).sqrt().multiply(0.5);
436                     T inv = quat[3].reciprocal().multiply(0.25);
437                     quat[0] = inv.multiply(ort[0][1].subtract(ort[1][0]));
438                     quat[1] = inv.multiply(ort[0][2].add(ort[2][0]));
439                     quat[2] = inv.multiply(ort[2][1].add(ort[1][2]));
440                 }
441             }
442         }
443 
444         return quat;
445 
446     }
447 
448     /** Revert a rotation.
449      * Build a rotation which reverse the effect of another
450      * rotation. This means that if r(u) = v, then r.revert(v) = u. The
451      * instance is not changed.
452      * @return a new rotation whose effect is the reverse of the effect
453      * of the instance
454      */
revert()455     public FieldRotation<T> revert() {
456         return new FieldRotation<T>(q0.negate(), q1, q2, q3, false);
457     }
458 
459     /** Get the scalar coordinate of the quaternion.
460      * @return scalar coordinate of the quaternion
461      */
getQ0()462     public T getQ0() {
463         return q0;
464     }
465 
466     /** Get the first coordinate of the vectorial part of the quaternion.
467      * @return first coordinate of the vectorial part of the quaternion
468      */
getQ1()469     public T getQ1() {
470         return q1;
471     }
472 
473     /** Get the second coordinate of the vectorial part of the quaternion.
474      * @return second coordinate of the vectorial part of the quaternion
475      */
getQ2()476     public T getQ2() {
477         return q2;
478     }
479 
480     /** Get the third coordinate of the vectorial part of the quaternion.
481      * @return third coordinate of the vectorial part of the quaternion
482      */
getQ3()483     public T getQ3() {
484         return q3;
485     }
486 
487     /** Get the normalized axis of the rotation.
488      * @return normalized axis of the rotation
489      * @see #FieldRotation(FieldVector3D, RealFieldElement)
490      * @deprecated as of 3.6, replaced with {@link #getAxis(RotationConvention)}
491      */
492     @Deprecated
getAxis()493     public FieldVector3D<T> getAxis() {
494         return getAxis(RotationConvention.VECTOR_OPERATOR);
495     }
496 
497     /** Get the normalized axis of the rotation.
498      * <p>
499      * Note that as {@link #getAngle()} always returns an angle
500      * between 0 and &pi;, changing the convention changes the
501      * direction of the axis, not the sign of the angle.
502      * </p>
503      * @param convention convention to use for the semantics of the angle
504      * @return normalized axis of the rotation
505      * @see #FieldRotation(FieldVector3D, RealFieldElement)
506      * @since 3.6
507      */
getAxis(final RotationConvention convention)508     public FieldVector3D<T> getAxis(final RotationConvention convention) {
509         final T squaredSine = q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3));
510         if (squaredSine.getReal() == 0) {
511             final Field<T> field = squaredSine.getField();
512             return new FieldVector3D<T>(convention == RotationConvention.VECTOR_OPERATOR ? field.getOne(): field.getOne().negate(),
513                                         field.getZero(),
514                                         field.getZero());
515         } else {
516             final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1;
517             if (q0.getReal() < 0) {
518                 T inverse = squaredSine.sqrt().reciprocal().multiply(sgn);
519                 return new FieldVector3D<T>(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse));
520             }
521             final T inverse = squaredSine.sqrt().reciprocal().negate().multiply(sgn);
522             return new FieldVector3D<T>(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse));
523         }
524     }
525 
526     /** Get the angle of the rotation.
527      * @return angle of the rotation (between 0 and &pi;)
528      * @see #FieldRotation(FieldVector3D, RealFieldElement)
529      */
getAngle()530     public T getAngle() {
531         if ((q0.getReal() < -0.1) || (q0.getReal() > 0.1)) {
532             return q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().asin().multiply(2);
533         } else if (q0.getReal() < 0) {
534             return q0.negate().acos().multiply(2);
535         }
536         return q0.acos().multiply(2);
537     }
538 
539     /** Get the Cardan or Euler angles corresponding to the instance.
540 
541      * <p>The equations show that each rotation can be defined by two
542      * different values of the Cardan or Euler angles set. For example
543      * if Cardan angles are used, the rotation defined by the angles
544      * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as
545      * the rotation defined by the angles &pi; + a<sub>1</sub>, &pi;
546      * - a<sub>2</sub> and &pi; + a<sub>3</sub>. This method implements
547      * the following arbitrary choices:</p>
548      * <ul>
549      *   <li>for Cardan angles, the chosen set is the one for which the
550      *   second angle is between -&pi;/2 and &pi;/2 (i.e its cosine is
551      *   positive),</li>
552      *   <li>for Euler angles, the chosen set is the one for which the
553      *   second angle is between 0 and &pi; (i.e its sine is positive).</li>
554      * </ul>
555 
556      * <p>Cardan and Euler angle have a very disappointing drawback: all
557      * of them have singularities. This means that if the instance is
558      * too close to the singularities corresponding to the given
559      * rotation order, it will be impossible to retrieve the angles. For
560      * Cardan angles, this is often called gimbal lock. There is
561      * <em>nothing</em> to do to prevent this, it is an intrinsic problem
562      * with Cardan and Euler representation (but not a problem with the
563      * rotation itself, which is perfectly well defined). For Cardan
564      * angles, singularities occur when the second angle is close to
565      * -&pi;/2 or +&pi;/2, for Euler angle singularities occur when the
566      * second angle is close to 0 or &pi;, this implies that the identity
567      * rotation is always singular for Euler angles!</p>
568 
569      * @param order rotation order to use
570      * @return an array of three angles, in the order specified by the set
571      * @exception CardanEulerSingularityException if the rotation is
572      * singular with respect to the angles set specified
573      * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)}
574      */
575     @Deprecated
getAngles(final RotationOrder order)576     public T[] getAngles(final RotationOrder order)
577         throws CardanEulerSingularityException {
578         return getAngles(order, RotationConvention.VECTOR_OPERATOR);
579     }
580 
581     /** Get the Cardan or Euler angles corresponding to the instance.
582 
583      * <p>The equations show that each rotation can be defined by two
584      * different values of the Cardan or Euler angles set. For example
585      * if Cardan angles are used, the rotation defined by the angles
586      * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as
587      * the rotation defined by the angles &pi; + a<sub>1</sub>, &pi;
588      * - a<sub>2</sub> and &pi; + a<sub>3</sub>. This method implements
589      * the following arbitrary choices:</p>
590      * <ul>
591      *   <li>for Cardan angles, the chosen set is the one for which the
592      *   second angle is between -&pi;/2 and &pi;/2 (i.e its cosine is
593      *   positive),</li>
594      *   <li>for Euler angles, the chosen set is the one for which the
595      *   second angle is between 0 and &pi; (i.e its sine is positive).</li>
596      * </ul>
597 
598      * <p>Cardan and Euler angle have a very disappointing drawback: all
599      * of them have singularities. This means that if the instance is
600      * too close to the singularities corresponding to the given
601      * rotation order, it will be impossible to retrieve the angles. For
602      * Cardan angles, this is often called gimbal lock. There is
603      * <em>nothing</em> to do to prevent this, it is an intrinsic problem
604      * with Cardan and Euler representation (but not a problem with the
605      * rotation itself, which is perfectly well defined). For Cardan
606      * angles, singularities occur when the second angle is close to
607      * -&pi;/2 or +&pi;/2, for Euler angle singularities occur when the
608      * second angle is close to 0 or &pi;, this implies that the identity
609      * rotation is always singular for Euler angles!</p>
610 
611      * @param order rotation order to use
612      * @param convention convention to use for the semantics of the angle
613      * @return an array of three angles, in the order specified by the set
614      * @exception CardanEulerSingularityException if the rotation is
615      * singular with respect to the angles set specified
616      * @since 3.6
617      */
getAngles(final RotationOrder order, RotationConvention convention)618     public T[] getAngles(final RotationOrder order, RotationConvention convention)
619         throws CardanEulerSingularityException {
620 
621         if (convention == RotationConvention.VECTOR_OPERATOR) {
622             if (order == RotationOrder.XYZ) {
623 
624                 // r (+K) coordinates are :
625                 //  sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi)
626                 // (-r) (+I) coordinates are :
627                 // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta)
628                 final // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
629                 FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
630                 final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
631                 if  ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
632                     throw new CardanEulerSingularityException(true);
633                 }
634                 return buildArray(v1.getY().negate().atan2(v1.getZ()),
635                                   v2.getZ().asin(),
636                                   v2.getY().negate().atan2(v2.getX()));
637 
638             } else if (order == RotationOrder.XZY) {
639 
640                 // r (+J) coordinates are :
641                 // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi)
642                 // (-r) (+I) coordinates are :
643                 // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi)
644                 // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
645                 final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
646                 final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
647                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
648                     throw new CardanEulerSingularityException(true);
649                 }
650                 return buildArray(v1.getZ().atan2(v1.getY()),
651                                   v2.getY().asin().negate(),
652                                   v2.getZ().atan2(v2.getX()));
653 
654             } else if (order == RotationOrder.YXZ) {
655 
656                 // r (+K) coordinates are :
657                 //  cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta)
658                 // (-r) (+J) coordinates are :
659                 // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi)
660                 // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
661                 final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
662                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
663                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
664                     throw new CardanEulerSingularityException(true);
665                 }
666                 return buildArray(v1.getX().atan2(v1.getZ()),
667                                   v2.getZ().asin().negate(),
668                                   v2.getX().atan2(v2.getY()));
669 
670             } else if (order == RotationOrder.YZX) {
671 
672                 // r (+I) coordinates are :
673                 // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta)
674                 // (-r) (+J) coordinates are :
675                 // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi)
676                 // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
677                 final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
678                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
679                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
680                     throw new CardanEulerSingularityException(true);
681                 }
682                 return buildArray(v1.getZ().negate().atan2(v1.getX()),
683                                   v2.getX().asin(),
684                                   v2.getZ().negate().atan2(v2.getY()));
685 
686             } else if (order == RotationOrder.ZXY) {
687 
688                 // r (+J) coordinates are :
689                 // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi)
690                 // (-r) (+K) coordinates are :
691                 // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi)
692                 // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
693                 final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
694                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
695                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
696                     throw new CardanEulerSingularityException(true);
697                 }
698                 return buildArray(v1.getX().negate().atan2(v1.getY()),
699                                   v2.getY().asin(),
700                                   v2.getX().negate().atan2(v2.getZ()));
701 
702             } else if (order == RotationOrder.ZYX) {
703 
704                 // r (+I) coordinates are :
705                 //  cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta)
706                 // (-r) (+K) coordinates are :
707                 // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta)
708                 // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
709                 final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
710                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
711                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
712                     throw new CardanEulerSingularityException(true);
713                 }
714                 return buildArray(v1.getY().atan2(v1.getX()),
715                                   v2.getX().asin().negate(),
716                                   v2.getY().atan2(v2.getZ()));
717 
718             } else if (order == RotationOrder.XYX) {
719 
720                 // r (+I) coordinates are :
721                 //  cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta)
722                 // (-r) (+I) coordinates are :
723                 // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2)
724                 // and we can choose to have theta in the interval [0 ; PI]
725                 final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
726                 final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
727                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
728                     throw new CardanEulerSingularityException(false);
729                 }
730                 return buildArray(v1.getY().atan2(v1.getZ().negate()),
731                                   v2.getX().acos(),
732                                   v2.getY().atan2(v2.getZ()));
733 
734             } else if (order == RotationOrder.XZX) {
735 
736                 // r (+I) coordinates are :
737                 //  cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi)
738                 // (-r) (+I) coordinates are :
739                 // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2)
740                 // and we can choose to have psi in the interval [0 ; PI]
741                 final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
742                 final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
743                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
744                     throw new CardanEulerSingularityException(false);
745                 }
746                 return buildArray(v1.getZ().atan2(v1.getY()),
747                                   v2.getX().acos(),
748                                   v2.getZ().atan2(v2.getY().negate()));
749 
750             } else if (order == RotationOrder.YXY) {
751 
752                 // r (+J) coordinates are :
753                 //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
754                 // (-r) (+J) coordinates are :
755                 // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
756                 // and we can choose to have phi in the interval [0 ; PI]
757                 final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
758                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
759                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
760                     throw new CardanEulerSingularityException(false);
761                 }
762                 return buildArray(v1.getX().atan2(v1.getZ()),
763                                   v2.getY().acos(),
764                                   v2.getX().atan2(v2.getZ().negate()));
765 
766             } else if (order == RotationOrder.YZY) {
767 
768                 // r (+J) coordinates are :
769                 //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
770                 // (-r) (+J) coordinates are :
771                 // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
772                 // and we can choose to have psi in the interval [0 ; PI]
773                 final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
774                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
775                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
776                     throw new CardanEulerSingularityException(false);
777                 }
778                 return buildArray(v1.getZ().atan2(v1.getX().negate()),
779                                   v2.getY().acos(),
780                                   v2.getZ().atan2(v2.getX()));
781 
782             } else if (order == RotationOrder.ZXZ) {
783 
784                 // r (+K) coordinates are :
785                 //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
786                 // (-r) (+K) coordinates are :
787                 // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
788                 // and we can choose to have phi in the interval [0 ; PI]
789                 final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
790                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
791                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
792                     throw new CardanEulerSingularityException(false);
793                 }
794                 return buildArray(v1.getX().atan2(v1.getY().negate()),
795                                   v2.getZ().acos(),
796                                   v2.getX().atan2(v2.getY()));
797 
798             } else { // last possibility is ZYZ
799 
800                 // r (+K) coordinates are :
801                 //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
802                 // (-r) (+K) coordinates are :
803                 // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
804                 // and we can choose to have theta in the interval [0 ; PI]
805                 final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
806                 final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
807                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
808                     throw new CardanEulerSingularityException(false);
809                 }
810                 return buildArray(v1.getY().atan2(v1.getX()),
811                                   v2.getZ().acos(),
812                                   v2.getY().atan2(v2.getX().negate()));
813 
814             }
815         } else {
816             if (order == RotationOrder.XYZ) {
817 
818                 // r (Vector3D.plusI) coordinates are :
819                 //  cos (theta) cos (psi), -cos (theta) sin (psi), sin (theta)
820                 // (-r) (Vector3D.plusK) coordinates are :
821                 // sin (theta), -sin (phi) cos (theta), cos (phi) cos (theta)
822                 // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
823                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
824                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
825                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
826                     throw new CardanEulerSingularityException(true);
827                 }
828                 return buildArray(v2.getY().negate().atan2(v2.getZ()),
829                                   v2.getX().asin(),
830                                   v1.getY().negate().atan2(v1.getX()));
831 
832             } else if (order == RotationOrder.XZY) {
833 
834                 // r (Vector3D.plusI) coordinates are :
835                 // cos (psi) cos (theta), -sin (psi), cos (psi) sin (theta)
836                 // (-r) (Vector3D.plusJ) coordinates are :
837                 // -sin (psi), cos (phi) cos (psi), sin (phi) cos (psi)
838                 // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
839                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
840                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
841                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
842                     throw new CardanEulerSingularityException(true);
843                 }
844                 return buildArray(v2.getZ().atan2(v2.getY()),
845                                   v2.getX().asin().negate(),
846                                   v1.getZ().atan2(v1.getX()));
847 
848             } else if (order == RotationOrder.YXZ) {
849 
850                 // r (Vector3D.plusJ) coordinates are :
851                 // cos (phi) sin (psi), cos (phi) cos (psi), -sin (phi)
852                 // (-r) (Vector3D.plusK) coordinates are :
853                 // sin (theta) cos (phi), -sin (phi), cos (theta) cos (phi)
854                 // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
855                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
856                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
857                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
858                     throw new CardanEulerSingularityException(true);
859                 }
860                 return buildArray(v2.getX().atan2(v2.getZ()),
861                                   v2.getY().asin().negate(),
862                                   v1.getX().atan2(v1.getY()));
863 
864             } else if (order == RotationOrder.YZX) {
865 
866                 // r (Vector3D.plusJ) coordinates are :
867                 // sin (psi), cos (psi) cos (phi), -cos (psi) sin (phi)
868                 // (-r) (Vector3D.plusI) coordinates are :
869                 // cos (theta) cos (psi), sin (psi), -sin (theta) cos (psi)
870                 // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
871                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
872                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
873                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
874                     throw new CardanEulerSingularityException(true);
875                 }
876                 return buildArray(v2.getZ().negate().atan2(v2.getX()),
877                                   v2.getY().asin(),
878                                   v1.getZ().negate().atan2(v1.getY()));
879 
880             } else if (order == RotationOrder.ZXY) {
881 
882                 // r (Vector3D.plusK) coordinates are :
883                 //  -cos (phi) sin (theta), sin (phi), cos (phi) cos (theta)
884                 // (-r) (Vector3D.plusJ) coordinates are :
885                 // -sin (psi) cos (phi), cos (psi) cos (phi), sin (phi)
886                 // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
887                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
888                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
889                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
890                     throw new CardanEulerSingularityException(true);
891                 }
892                 return buildArray(v2.getX().negate().atan2(v2.getY()),
893                                   v2.getZ().asin(),
894                                   v1.getX().negate().atan2(v1.getZ()));
895 
896             } else if (order == RotationOrder.ZYX) {
897 
898                 // r (Vector3D.plusK) coordinates are :
899                 //  -sin (theta), cos (theta) sin (phi), cos (theta) cos (phi)
900                 // (-r) (Vector3D.plusI) coordinates are :
901                 // cos (psi) cos (theta), sin (psi) cos (theta), -sin (theta)
902                 // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
903                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
904                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
905                 if  ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
906                     throw new CardanEulerSingularityException(true);
907                 }
908                 return buildArray(v2.getY().atan2(v2.getX()),
909                                   v2.getZ().asin().negate(),
910                                   v1.getY().atan2(v1.getZ()));
911 
912             } else if (order == RotationOrder.XYX) {
913 
914                 // r (Vector3D.plusI) coordinates are :
915                 //  cos (theta), sin (phi2) sin (theta), cos (phi2) sin (theta)
916                 // (-r) (Vector3D.plusI) coordinates are :
917                 // cos (theta), sin (theta) sin (phi1), -sin (theta) cos (phi1)
918                 // and we can choose to have theta in the interval [0 ; PI]
919                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
920                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
921                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
922                     throw new CardanEulerSingularityException(false);
923                 }
924                 return buildArray(v2.getY().atan2(v2.getZ().negate()),
925                                   v2.getX().acos(),
926                                   v1.getY().atan2(v1.getZ()));
927 
928             } else if (order == RotationOrder.XZX) {
929 
930                 // r (Vector3D.plusI) coordinates are :
931                 //  cos (psi), -cos (phi2) sin (psi), sin (phi2) sin (psi)
932                 // (-r) (Vector3D.plusI) coordinates are :
933                 // cos (psi), sin (psi) cos (phi1), sin (psi) sin (phi1)
934                 // and we can choose to have psi in the interval [0 ; PI]
935                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
936                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
937                 if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
938                     throw new CardanEulerSingularityException(false);
939                 }
940                 return buildArray(v2.getZ().atan2(v2.getY()),
941                                   v2.getX().acos(),
942                                   v1.getZ().atan2(v1.getY().negate()));
943 
944             } else if (order == RotationOrder.YXY) {
945 
946                 // r (Vector3D.plusJ) coordinates are :
947                 // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
948                 // (-r) (Vector3D.plusJ) coordinates are :
949                 //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
950                 // and we can choose to have phi in the interval [0 ; PI]
951                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
952                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
953                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
954                     throw new CardanEulerSingularityException(false);
955                 }
956                 return buildArray(v2.getX().atan2(v2.getZ()),
957                                   v2.getY().acos(),
958                                   v1.getX().atan2(v1.getZ().negate()));
959 
960             } else if (order == RotationOrder.YZY) {
961 
962                 // r (Vector3D.plusJ) coordinates are :
963                 // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
964                 // (-r) (Vector3D.plusJ) coordinates are :
965                 //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
966                 // and we can choose to have psi in the interval [0 ; PI]
967                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
968                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
969                 if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
970                     throw new CardanEulerSingularityException(false);
971                 }
972                 return buildArray(v2.getZ().atan2(v2.getX().negate()),
973                                   v2.getY().acos(),
974                                   v1.getZ().atan2(v1.getX()));
975 
976             } else if (order == RotationOrder.ZXZ) {
977 
978                 // r (Vector3D.plusK) coordinates are :
979                 // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
980                 // (-r) (Vector3D.plusK) coordinates are :
981                 //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
982                 // and we can choose to have phi in the interval [0 ; PI]
983                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
984                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
985                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
986                     throw new CardanEulerSingularityException(false);
987                 }
988                 return buildArray(v2.getX().atan2(v2.getY().negate()),
989                                   v2.getZ().acos(),
990                                   v1.getX().atan2(v1.getY()));
991 
992             } else { // last possibility is ZYZ
993 
994                 // r (Vector3D.plusK) coordinates are :
995                 // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
996                 // (-r) (Vector3D.plusK) coordinates are :
997                 //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
998                 // and we can choose to have theta in the interval [0 ; PI]
999                 FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
1000                 FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
1001                 if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
1002                     throw new CardanEulerSingularityException(false);
1003                 }
1004                 return buildArray(v2.getY().atan2(v2.getX()),
1005                                   v2.getZ().acos(),
1006                                   v1.getY().atan2(v1.getX().negate()));
1007 
1008             }
1009         }
1010 
1011     }
1012 
1013     /** Create a dimension 3 array.
1014      * @param a0 first array element
1015      * @param a1 second array element
1016      * @param a2 third array element
1017      * @return new array
1018      */
buildArray(final T a0, final T a1, final T a2)1019     private T[] buildArray(final T a0, final T a1, final T a2) {
1020         final T[] array = MathArrays.buildArray(a0.getField(), 3);
1021         array[0] = a0;
1022         array[1] = a1;
1023         array[2] = a2;
1024         return array;
1025     }
1026 
1027     /** Create a constant vector.
1028      * @param x abscissa
1029      * @param y ordinate
1030      * @param z height
1031      * @return a constant vector
1032      */
vector(final double x, final double y, final double z)1033     private FieldVector3D<T> vector(final double x, final double y, final double z) {
1034         final T zero = q0.getField().getZero();
1035         return new FieldVector3D<T>(zero.add(x), zero.add(y), zero.add(z));
1036     }
1037 
1038     /** Get the 3X3 matrix corresponding to the instance
1039      * @return the matrix corresponding to the instance
1040      */
getMatrix()1041     public T[][] getMatrix() {
1042 
1043         // products
1044         final T q0q0  = q0.multiply(q0);
1045         final T q0q1  = q0.multiply(q1);
1046         final T q0q2  = q0.multiply(q2);
1047         final T q0q3  = q0.multiply(q3);
1048         final T q1q1  = q1.multiply(q1);
1049         final T q1q2  = q1.multiply(q2);
1050         final T q1q3  = q1.multiply(q3);
1051         final T q2q2  = q2.multiply(q2);
1052         final T q2q3  = q2.multiply(q3);
1053         final T q3q3  = q3.multiply(q3);
1054 
1055         // create the matrix
1056         final T[][] m = MathArrays.buildArray(q0.getField(), 3, 3);
1057 
1058         m [0][0] = q0q0.add(q1q1).multiply(2).subtract(1);
1059         m [1][0] = q1q2.subtract(q0q3).multiply(2);
1060         m [2][0] = q1q3.add(q0q2).multiply(2);
1061 
1062         m [0][1] = q1q2.add(q0q3).multiply(2);
1063         m [1][1] = q0q0.add(q2q2).multiply(2).subtract(1);
1064         m [2][1] = q2q3.subtract(q0q1).multiply(2);
1065 
1066         m [0][2] = q1q3.subtract(q0q2).multiply(2);
1067         m [1][2] = q2q3.add(q0q1).multiply(2);
1068         m [2][2] = q0q0.add(q3q3).multiply(2).subtract(1);
1069 
1070         return m;
1071 
1072     }
1073 
1074     /** Convert to a constant vector without derivatives.
1075      * @return a constant vector
1076      */
toRotation()1077     public Rotation toRotation() {
1078         return new Rotation(q0.getReal(), q1.getReal(), q2.getReal(), q3.getReal(), false);
1079     }
1080 
1081     /** Apply the rotation to a vector.
1082      * @param u vector to apply the rotation to
1083      * @return a new vector which is the image of u by the rotation
1084      */
applyTo(final FieldVector3D<T> u)1085     public FieldVector3D<T> applyTo(final FieldVector3D<T> u) {
1086 
1087         final T x = u.getX();
1088         final T y = u.getY();
1089         final T z = u.getZ();
1090 
1091         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1092 
1093         return new FieldVector3D<T>(q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
1094                                     q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
1095                                     q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
1096 
1097     }
1098 
1099     /** Apply the rotation to a vector.
1100      * @param u vector to apply the rotation to
1101      * @return a new vector which is the image of u by the rotation
1102      */
applyTo(final Vector3D u)1103     public FieldVector3D<T> applyTo(final Vector3D u) {
1104 
1105         final double x = u.getX();
1106         final double y = u.getY();
1107         final double z = u.getZ();
1108 
1109         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1110 
1111         return new FieldVector3D<T>(q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
1112                                     q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
1113                                     q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
1114 
1115     }
1116 
1117     /** Apply the rotation to a vector stored in an array.
1118      * @param in an array with three items which stores vector to rotate
1119      * @param out an array with three items to put result to (it can be the same
1120      * array as in)
1121      */
applyTo(final T[] in, final T[] out)1122     public void applyTo(final T[] in, final T[] out) {
1123 
1124         final T x = in[0];
1125         final T y = in[1];
1126         final T z = in[2];
1127 
1128         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1129 
1130         out[0] = q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
1131         out[1] = q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
1132         out[2] = q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
1133 
1134     }
1135 
1136     /** Apply the rotation to a vector stored in an array.
1137      * @param in an array with three items which stores vector to rotate
1138      * @param out an array with three items to put result to
1139      */
applyTo(final double[] in, final T[] out)1140     public void applyTo(final double[] in, final T[] out) {
1141 
1142         final double x = in[0];
1143         final double y = in[1];
1144         final double z = in[2];
1145 
1146         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1147 
1148         out[0] = q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
1149         out[1] = q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
1150         out[2] = q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
1151 
1152     }
1153 
1154     /** Apply a rotation to a vector.
1155      * @param r rotation to apply
1156      * @param u vector to apply the rotation to
1157      * @param <T> the type of the field elements
1158      * @return a new vector which is the image of u by the rotation
1159      */
applyTo(final Rotation r, final FieldVector3D<T> u)1160     public static <T extends RealFieldElement<T>> FieldVector3D<T> applyTo(final Rotation r, final FieldVector3D<T> u) {
1161 
1162         final T x = u.getX();
1163         final T y = u.getY();
1164         final T z = u.getZ();
1165 
1166         final T s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3()));
1167 
1168         return new FieldVector3D<T>(x.multiply(r.getQ0()).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(r.getQ0()).add(s.multiply(r.getQ1())).multiply(2).subtract(x),
1169                                     y.multiply(r.getQ0()).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(r.getQ0()).add(s.multiply(r.getQ2())).multiply(2).subtract(y),
1170                                     z.multiply(r.getQ0()).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(r.getQ0()).add(s.multiply(r.getQ3())).multiply(2).subtract(z));
1171 
1172     }
1173 
1174     /** Apply the inverse of the rotation to a vector.
1175      * @param u vector to apply the inverse of the rotation to
1176      * @return a new vector which such that u is its image by the rotation
1177      */
applyInverseTo(final FieldVector3D<T> u)1178     public FieldVector3D<T> applyInverseTo(final FieldVector3D<T> u) {
1179 
1180         final T x = u.getX();
1181         final T y = u.getY();
1182         final T z = u.getZ();
1183 
1184         final T s  = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1185         final T m0 = q0.negate();
1186 
1187         return new FieldVector3D<T>(m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
1188                                     m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
1189                                     m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
1190 
1191     }
1192 
1193     /** Apply the inverse of the rotation to a vector.
1194      * @param u vector to apply the inverse of the rotation to
1195      * @return a new vector which such that u is its image by the rotation
1196      */
applyInverseTo(final Vector3D u)1197     public FieldVector3D<T> applyInverseTo(final Vector3D u) {
1198 
1199         final double x = u.getX();
1200         final double y = u.getY();
1201         final double z = u.getZ();
1202 
1203         final T s  = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1204         final T m0 = q0.negate();
1205 
1206         return new FieldVector3D<T>(m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
1207                                     m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
1208                                     m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
1209 
1210     }
1211 
1212     /** Apply the inverse of the rotation to a vector stored in an array.
1213      * @param in an array with three items which stores vector to rotate
1214      * @param out an array with three items to put result to (it can be the same
1215      * array as in)
1216      */
applyInverseTo(final T[] in, final T[] out)1217     public void applyInverseTo(final T[] in, final T[] out) {
1218 
1219         final T x = in[0];
1220         final T y = in[1];
1221         final T z = in[2];
1222 
1223         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1224         final T m0 = q0.negate();
1225 
1226         out[0] = m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
1227         out[1] = m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
1228         out[2] = m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
1229 
1230     }
1231 
1232     /** Apply the inverse of the rotation to a vector stored in an array.
1233      * @param in an array with three items which stores vector to rotate
1234      * @param out an array with three items to put result to
1235      */
applyInverseTo(final double[] in, final T[] out)1236     public void applyInverseTo(final double[] in, final T[] out) {
1237 
1238         final double x = in[0];
1239         final double y = in[1];
1240         final double z = in[2];
1241 
1242         final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
1243         final T m0 = q0.negate();
1244 
1245         out[0] = m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
1246         out[1] = m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
1247         out[2] = m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
1248 
1249     }
1250 
1251     /** Apply the inverse of a rotation to a vector.
1252      * @param r rotation to apply
1253      * @param u vector to apply the inverse of the rotation to
1254      * @param <T> the type of the field elements
1255      * @return a new vector which such that u is its image by the rotation
1256      */
applyInverseTo(final Rotation r, final FieldVector3D<T> u)1257     public static <T extends RealFieldElement<T>> FieldVector3D<T> applyInverseTo(final Rotation r, final FieldVector3D<T> u) {
1258 
1259         final T x = u.getX();
1260         final T y = u.getY();
1261         final T z = u.getZ();
1262 
1263         final T s  = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3()));
1264         final double m0 = -r.getQ0();
1265 
1266         return new FieldVector3D<T>(x.multiply(m0).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(m0).add(s.multiply(r.getQ1())).multiply(2).subtract(x),
1267                                     y.multiply(m0).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(m0).add(s.multiply(r.getQ2())).multiply(2).subtract(y),
1268                                     z.multiply(m0).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(m0).add(s.multiply(r.getQ3())).multiply(2).subtract(z));
1269 
1270     }
1271 
1272     /** Apply the instance to another rotation.
1273      * <p>
1274      * Calling this method is equivalent to call
1275      * {@link #compose(FieldRotation, RotationConvention)
1276      * compose(r, RotationConvention.VECTOR_OPERATOR)}.
1277      * </p>
1278      * @param r rotation to apply the rotation to
1279      * @return a new rotation which is the composition of r by the instance
1280      */
applyTo(final FieldRotation<T> r)1281     public FieldRotation<T> applyTo(final FieldRotation<T> r) {
1282         return compose(r, RotationConvention.VECTOR_OPERATOR);
1283     }
1284 
1285     /** Compose the instance with another rotation.
1286      * <p>
1287      * If the semantics of the rotations composition corresponds to a
1288      * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
1289      * applying the instance to a rotation is computing the composition
1290      * in an order compliant with the following rule : let {@code u} be any
1291      * vector and {@code v} its image by {@code r1} (i.e.
1292      * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by
1293      * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then
1294      * {@code w = comp.applyTo(u)}, where
1295      * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}.
1296      * </p>
1297      * <p>
1298      * If the semantics of the rotations composition corresponds to a
1299      * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
1300      * the application order will be reversed. So keeping the exact same
1301      * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
1302      * and  {@code comp} as above, {@code comp} could also be computed as
1303      * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}.
1304      * </p>
1305      * @param r rotation to apply the rotation to
1306      * @param convention convention to use for the semantics of the angle
1307      * @return a new rotation which is the composition of r by the instance
1308      */
compose(final FieldRotation<T> r, final RotationConvention convention)1309     public FieldRotation<T> compose(final FieldRotation<T> r, final RotationConvention convention) {
1310         return convention == RotationConvention.VECTOR_OPERATOR ?
1311                              composeInternal(r) : r.composeInternal(this);
1312     }
1313 
1314     /** Compose the instance with another rotation using vector operator convention.
1315      * @param r rotation to apply the rotation to
1316      * @return a new rotation which is the composition of r by the instance
1317      * using vector operator convention
1318      */
composeInternal(final FieldRotation<T> r)1319     private FieldRotation<T> composeInternal(final FieldRotation<T> r) {
1320         return new FieldRotation<T>(r.q0.multiply(q0).subtract(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))),
1321                                     r.q1.multiply(q0).add(r.q0.multiply(q1)).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))),
1322                                     r.q2.multiply(q0).add(r.q0.multiply(q2)).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))),
1323                                     r.q3.multiply(q0).add(r.q0.multiply(q3)).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))),
1324                                     false);
1325     }
1326 
1327     /** Apply the instance to another rotation.
1328      * <p>
1329      * Calling this method is equivalent to call
1330      * {@link #compose(Rotation, RotationConvention)
1331      * compose(r, RotationConvention.VECTOR_OPERATOR)}.
1332      * </p>
1333      * @param r rotation to apply the rotation to
1334      * @return a new rotation which is the composition of r by the instance
1335      */
applyTo(final Rotation r)1336     public FieldRotation<T> applyTo(final Rotation r) {
1337         return compose(r, RotationConvention.VECTOR_OPERATOR);
1338     }
1339 
1340     /** Compose the instance with another rotation.
1341      * <p>
1342      * If the semantics of the rotations composition corresponds to a
1343      * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
1344      * applying the instance to a rotation is computing the composition
1345      * in an order compliant with the following rule : let {@code u} be any
1346      * vector and {@code v} its image by {@code r1} (i.e.
1347      * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by
1348      * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then
1349      * {@code w = comp.applyTo(u)}, where
1350      * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}.
1351      * </p>
1352      * <p>
1353      * If the semantics of the rotations composition corresponds to a
1354      * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
1355      * the application order will be reversed. So keeping the exact same
1356      * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
1357      * and  {@code comp} as above, {@code comp} could also be computed as
1358      * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}.
1359      * </p>
1360      * @param r rotation to apply the rotation to
1361      * @param convention convention to use for the semantics of the angle
1362      * @return a new rotation which is the composition of r by the instance
1363      */
compose(final Rotation r, final RotationConvention convention)1364     public FieldRotation<T> compose(final Rotation r, final RotationConvention convention) {
1365         return convention == RotationConvention.VECTOR_OPERATOR ?
1366                              composeInternal(r) : applyTo(r, this);
1367     }
1368 
1369     /** Compose the instance with another rotation using vector operator convention.
1370      * @param r rotation to apply the rotation to
1371      * @return a new rotation which is the composition of r by the instance
1372      * using vector operator convention
1373      */
composeInternal(final Rotation r)1374     private FieldRotation<T> composeInternal(final Rotation r) {
1375         return new FieldRotation<T>(q0.multiply(r.getQ0()).subtract(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))),
1376                         q0.multiply(r.getQ1()).add(q1.multiply(r.getQ0())).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))),
1377                         q0.multiply(r.getQ2()).add(q2.multiply(r.getQ0())).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))),
1378                         q0.multiply(r.getQ3()).add(q3.multiply(r.getQ0())).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))),
1379                         false);
1380     }
1381 
1382     /** Apply a rotation to another rotation.
1383      * Applying a rotation to another rotation is computing the composition
1384      * in an order compliant with the following rule : let u be any
1385      * vector and v its image by rInner (i.e. rInner.applyTo(u) = v), let w be the image
1386      * of v by rOuter (i.e. rOuter.applyTo(v) = w), then w = comp.applyTo(u),
1387      * where comp = applyTo(rOuter, rInner).
1388      * @param r1 rotation to apply
1389      * @param rInner rotation to apply the rotation to
1390      * @param <T> the type of the field elements
1391      * @return a new rotation which is the composition of r by the instance
1392      */
applyTo(final Rotation r1, final FieldRotation<T> rInner)1393     public static <T extends RealFieldElement<T>> FieldRotation<T> applyTo(final Rotation r1, final FieldRotation<T> rInner) {
1394         return new FieldRotation<T>(rInner.q0.multiply(r1.getQ0()).subtract(rInner.q1.multiply(r1.getQ1()).add(rInner.q2.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ3()))),
1395                                     rInner.q1.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ1())).add(rInner.q2.multiply(r1.getQ3()).subtract(rInner.q3.multiply(r1.getQ2()))),
1396                                     rInner.q2.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ1()).subtract(rInner.q1.multiply(r1.getQ3()))),
1397                                     rInner.q3.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ3())).add(rInner.q1.multiply(r1.getQ2()).subtract(rInner.q2.multiply(r1.getQ1()))),
1398                                     false);
1399     }
1400 
1401     /** Apply the inverse of the instance to another rotation.
1402      * <p>
1403      * Calling this method is equivalent to call
1404      * {@link #composeInverse(FieldRotation, RotationConvention)
1405      * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}.
1406      * </p>
1407      * @param r rotation to apply the rotation to
1408      * @return a new rotation which is the composition of r by the inverse
1409      * of the instance
1410      */
applyInverseTo(final FieldRotation<T> r)1411     public FieldRotation<T> applyInverseTo(final FieldRotation<T> r) {
1412         return composeInverse(r, RotationConvention.VECTOR_OPERATOR);
1413     }
1414 
1415     /** Compose the inverse of the instance with another rotation.
1416      * <p>
1417      * If the semantics of the rotations composition corresponds to a
1418      * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
1419      * applying the inverse of the instance to a rotation is computing
1420      * the composition in an order compliant with the following rule :
1421      * let {@code u} be any vector and {@code v} its image by {@code r1}
1422      * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image
1423      * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}).
1424      * Then {@code w = comp.applyTo(u)}, where
1425      * {@code comp = r2.composeInverse(r1)}.
1426      * </p>
1427      * <p>
1428      * If the semantics of the rotations composition corresponds to a
1429      * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
1430      * the application order will be reversed, which means it is the
1431      * <em>innermost</em> rotation that will be reversed. So keeping the exact same
1432      * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
1433      * and  {@code comp} as above, {@code comp} could also be computed as
1434      * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}.
1435      * </p>
1436      * @param r rotation to apply the rotation to
1437      * @param convention convention to use for the semantics of the angle
1438      * @return a new rotation which is the composition of r by the inverse
1439      * of the instance
1440      */
composeInverse(final FieldRotation<T> r, final RotationConvention convention)1441     public FieldRotation<T> composeInverse(final FieldRotation<T> r, final RotationConvention convention) {
1442         return convention == RotationConvention.VECTOR_OPERATOR ?
1443                              composeInverseInternal(r) : r.composeInternal(revert());
1444     }
1445 
1446     /** Compose the inverse of the instance with another rotation
1447      * using vector operator convention.
1448      * @param r rotation to apply the rotation to
1449      * @return a new rotation which is the composition of r by the inverse
1450      * of the instance using vector operator convention
1451      */
composeInverseInternal(FieldRotation<T> r)1452     private FieldRotation<T> composeInverseInternal(FieldRotation<T> r) {
1453         return new FieldRotation<T>(r.q0.multiply(q0).add(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))).negate(),
1454                                     r.q0.multiply(q1).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))).subtract(r.q1.multiply(q0)),
1455                                     r.q0.multiply(q2).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))).subtract(r.q2.multiply(q0)),
1456                                     r.q0.multiply(q3).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))).subtract(r.q3.multiply(q0)),
1457                                     false);
1458     }
1459 
1460     /** Apply the inverse of the instance to another rotation.
1461      * <p>
1462      * Calling this method is equivalent to call
1463      * {@link #composeInverse(Rotation, RotationConvention)
1464      * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}.
1465      * </p>
1466      * @param r rotation to apply the rotation to
1467      * @return a new rotation which is the composition of r by the inverse
1468      * of the instance
1469      */
applyInverseTo(final Rotation r)1470     public FieldRotation<T> applyInverseTo(final Rotation r) {
1471         return composeInverse(r, RotationConvention.VECTOR_OPERATOR);
1472     }
1473 
1474     /** Compose the inverse of the instance with another rotation.
1475      * <p>
1476      * If the semantics of the rotations composition corresponds to a
1477      * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
1478      * applying the inverse of the instance to a rotation is computing
1479      * the composition in an order compliant with the following rule :
1480      * let {@code u} be any vector and {@code v} its image by {@code r1}
1481      * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image
1482      * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}).
1483      * Then {@code w = comp.applyTo(u)}, where
1484      * {@code comp = r2.composeInverse(r1)}.
1485      * </p>
1486      * <p>
1487      * If the semantics of the rotations composition corresponds to a
1488      * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
1489      * the application order will be reversed, which means it is the
1490      * <em>innermost</em> rotation that will be reversed. So keeping the exact same
1491      * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
1492      * and  {@code comp} as above, {@code comp} could also be computed as
1493      * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}.
1494      * </p>
1495      * @param r rotation to apply the rotation to
1496      * @param convention convention to use for the semantics of the angle
1497      * @return a new rotation which is the composition of r by the inverse
1498      * of the instance
1499      */
composeInverse(final Rotation r, final RotationConvention convention)1500     public FieldRotation<T> composeInverse(final Rotation r, final RotationConvention convention) {
1501         return convention == RotationConvention.VECTOR_OPERATOR ?
1502                              composeInverseInternal(r) : applyTo(r, revert());
1503     }
1504 
1505     /** Compose the inverse of the instance with another rotation
1506      * using vector operator convention.
1507      * @param r rotation to apply the rotation to
1508      * @return a new rotation which is the composition of r by the inverse
1509      * of the instance using vector operator convention
1510      */
composeInverseInternal(Rotation r)1511     private FieldRotation<T> composeInverseInternal(Rotation r) {
1512         return new FieldRotation<T>(q0.multiply(r.getQ0()).add(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))).negate(),
1513                                     q1.multiply(r.getQ0()).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))).subtract(q0.multiply(r.getQ1())),
1514                                     q2.multiply(r.getQ0()).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))).subtract(q0.multiply(r.getQ2())),
1515                                     q3.multiply(r.getQ0()).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))).subtract(q0.multiply(r.getQ3())),
1516                                     false);
1517     }
1518 
1519     /** Apply the inverse of a rotation to another rotation.
1520      * Applying the inverse of a rotation to another rotation is computing
1521      * the composition in an order compliant with the following rule :
1522      * let u be any vector and v its image by rInner (i.e. rInner.applyTo(u) = v),
1523      * let w be the inverse image of v by rOuter
1524      * (i.e. rOuter.applyInverseTo(v) = w), then w = comp.applyTo(u), where
1525      * comp = applyInverseTo(rOuter, rInner).
1526      * @param rOuter rotation to apply the rotation to
1527      * @param rInner rotation to apply the rotation to
1528      * @param <T> the type of the field elements
1529      * @return a new rotation which is the composition of r by the inverse
1530      * of the instance
1531      */
applyInverseTo(final Rotation rOuter, final FieldRotation<T> rInner)1532     public static <T extends RealFieldElement<T>> FieldRotation<T> applyInverseTo(final Rotation rOuter, final FieldRotation<T> rInner) {
1533         return new FieldRotation<T>(rInner.q0.multiply(rOuter.getQ0()).add(rInner.q1.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ2())).add(rInner.q3.multiply(rOuter.getQ3()))).negate(),
1534                                     rInner.q0.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ3()).subtract(rInner.q3.multiply(rOuter.getQ2()))).subtract(rInner.q1.multiply(rOuter.getQ0())),
1535                                     rInner.q0.multiply(rOuter.getQ2()).add(rInner.q3.multiply(rOuter.getQ1()).subtract(rInner.q1.multiply(rOuter.getQ3()))).subtract(rInner.q2.multiply(rOuter.getQ0())),
1536                                     rInner.q0.multiply(rOuter.getQ3()).add(rInner.q1.multiply(rOuter.getQ2()).subtract(rInner.q2.multiply(rOuter.getQ1()))).subtract(rInner.q3.multiply(rOuter.getQ0())),
1537                                     false);
1538     }
1539 
1540     /** Perfect orthogonality on a 3X3 matrix.
1541      * @param m initial matrix (not exactly orthogonal)
1542      * @param threshold convergence threshold for the iterative
1543      * orthogonality correction (convergence is reached when the
1544      * difference between two steps of the Frobenius norm of the
1545      * correction is below this threshold)
1546      * @return an orthogonal matrix close to m
1547      * @exception NotARotationMatrixException if the matrix cannot be
1548      * orthogonalized with the given threshold after 10 iterations
1549      */
orthogonalizeMatrix(final T[][] m, final double threshold)1550     private T[][] orthogonalizeMatrix(final T[][] m, final double threshold)
1551         throws NotARotationMatrixException {
1552 
1553         T x00 = m[0][0];
1554         T x01 = m[0][1];
1555         T x02 = m[0][2];
1556         T x10 = m[1][0];
1557         T x11 = m[1][1];
1558         T x12 = m[1][2];
1559         T x20 = m[2][0];
1560         T x21 = m[2][1];
1561         T x22 = m[2][2];
1562         double fn = 0;
1563         double fn1;
1564 
1565         final T[][] o = MathArrays.buildArray(m[0][0].getField(), 3, 3);
1566 
1567         // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M)
1568         int i = 0;
1569         while (++i < 11) {
1570 
1571             // Mt.Xn
1572             final T mx00 = m[0][0].multiply(x00).add(m[1][0].multiply(x10)).add(m[2][0].multiply(x20));
1573             final T mx10 = m[0][1].multiply(x00).add(m[1][1].multiply(x10)).add(m[2][1].multiply(x20));
1574             final T mx20 = m[0][2].multiply(x00).add(m[1][2].multiply(x10)).add(m[2][2].multiply(x20));
1575             final T mx01 = m[0][0].multiply(x01).add(m[1][0].multiply(x11)).add(m[2][0].multiply(x21));
1576             final T mx11 = m[0][1].multiply(x01).add(m[1][1].multiply(x11)).add(m[2][1].multiply(x21));
1577             final T mx21 = m[0][2].multiply(x01).add(m[1][2].multiply(x11)).add(m[2][2].multiply(x21));
1578             final T mx02 = m[0][0].multiply(x02).add(m[1][0].multiply(x12)).add(m[2][0].multiply(x22));
1579             final T mx12 = m[0][1].multiply(x02).add(m[1][1].multiply(x12)).add(m[2][1].multiply(x22));
1580             final T mx22 = m[0][2].multiply(x02).add(m[1][2].multiply(x12)).add(m[2][2].multiply(x22));
1581 
1582             // Xn+1
1583             o[0][0] = x00.subtract(x00.multiply(mx00).add(x01.multiply(mx10)).add(x02.multiply(mx20)).subtract(m[0][0]).multiply(0.5));
1584             o[0][1] = x01.subtract(x00.multiply(mx01).add(x01.multiply(mx11)).add(x02.multiply(mx21)).subtract(m[0][1]).multiply(0.5));
1585             o[0][2] = x02.subtract(x00.multiply(mx02).add(x01.multiply(mx12)).add(x02.multiply(mx22)).subtract(m[0][2]).multiply(0.5));
1586             o[1][0] = x10.subtract(x10.multiply(mx00).add(x11.multiply(mx10)).add(x12.multiply(mx20)).subtract(m[1][0]).multiply(0.5));
1587             o[1][1] = x11.subtract(x10.multiply(mx01).add(x11.multiply(mx11)).add(x12.multiply(mx21)).subtract(m[1][1]).multiply(0.5));
1588             o[1][2] = x12.subtract(x10.multiply(mx02).add(x11.multiply(mx12)).add(x12.multiply(mx22)).subtract(m[1][2]).multiply(0.5));
1589             o[2][0] = x20.subtract(x20.multiply(mx00).add(x21.multiply(mx10)).add(x22.multiply(mx20)).subtract(m[2][0]).multiply(0.5));
1590             o[2][1] = x21.subtract(x20.multiply(mx01).add(x21.multiply(mx11)).add(x22.multiply(mx21)).subtract(m[2][1]).multiply(0.5));
1591             o[2][2] = x22.subtract(x20.multiply(mx02).add(x21.multiply(mx12)).add(x22.multiply(mx22)).subtract(m[2][2]).multiply(0.5));
1592 
1593             // correction on each elements
1594             final double corr00 = o[0][0].getReal() - m[0][0].getReal();
1595             final double corr01 = o[0][1].getReal() - m[0][1].getReal();
1596             final double corr02 = o[0][2].getReal() - m[0][2].getReal();
1597             final double corr10 = o[1][0].getReal() - m[1][0].getReal();
1598             final double corr11 = o[1][1].getReal() - m[1][1].getReal();
1599             final double corr12 = o[1][2].getReal() - m[1][2].getReal();
1600             final double corr20 = o[2][0].getReal() - m[2][0].getReal();
1601             final double corr21 = o[2][1].getReal() - m[2][1].getReal();
1602             final double corr22 = o[2][2].getReal() - m[2][2].getReal();
1603 
1604             // Frobenius norm of the correction
1605             fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 +
1606                   corr10 * corr10 + corr11 * corr11 + corr12 * corr12 +
1607                   corr20 * corr20 + corr21 * corr21 + corr22 * corr22;
1608 
1609             // convergence test
1610             if (FastMath.abs(fn1 - fn) <= threshold) {
1611                 return o;
1612             }
1613 
1614             // prepare next iteration
1615             x00 = o[0][0];
1616             x01 = o[0][1];
1617             x02 = o[0][2];
1618             x10 = o[1][0];
1619             x11 = o[1][1];
1620             x12 = o[1][2];
1621             x20 = o[2][0];
1622             x21 = o[2][1];
1623             x22 = o[2][2];
1624             fn  = fn1;
1625 
1626         }
1627 
1628         // the algorithm did not converge after 10 iterations
1629         throw new NotARotationMatrixException(LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX,
1630                                               i - 1);
1631 
1632     }
1633 
1634     /** Compute the <i>distance</i> between two rotations.
1635      * <p>The <i>distance</i> is intended here as a way to check if two
1636      * rotations are almost similar (i.e. they transform vectors the same way)
1637      * or very different. It is mathematically defined as the angle of
1638      * the rotation r that prepended to one of the rotations gives the other
1639      * one:</p>
1640      * <pre>
1641      *        r<sub>1</sub>(r) = r<sub>2</sub>
1642      * </pre>
1643      * <p>This distance is an angle between 0 and &pi;. Its value is the smallest
1644      * possible upper bound of the angle in radians between r<sub>1</sub>(v)
1645      * and r<sub>2</sub>(v) for all possible vectors v. This upper bound is
1646      * reached for some v. The distance is equal to 0 if and only if the two
1647      * rotations are identical.</p>
1648      * <p>Comparing two rotations should always be done using this value rather
1649      * than for example comparing the components of the quaternions. It is much
1650      * more stable, and has a geometric meaning. Also comparing quaternions
1651      * components is error prone since for example quaternions (0.36, 0.48, -0.48, -0.64)
1652      * and (-0.36, -0.48, 0.48, 0.64) represent exactly the same rotation despite
1653      * their components are different (they are exact opposites).</p>
1654      * @param r1 first rotation
1655      * @param r2 second rotation
1656      * @param <T> the type of the field elements
1657      * @return <i>distance</i> between r1 and r2
1658      */
distance(final FieldRotation<T> r1, final FieldRotation<T> r2)1659     public static <T extends RealFieldElement<T>> T distance(final FieldRotation<T> r1, final FieldRotation<T> r2) {
1660         return r1.composeInverseInternal(r2).getAngle();
1661     }
1662 
1663 }
1664