001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.math3.geometry.euclidean.threed; 019 020import java.io.Serializable; 021 022import org.apache.commons.math3.RealFieldElement; 023import org.apache.commons.math3.Field; 024import org.apache.commons.math3.exception.MathArithmeticException; 025import org.apache.commons.math3.exception.MathIllegalArgumentException; 026import org.apache.commons.math3.exception.util.LocalizedFormats; 027import org.apache.commons.math3.util.FastMath; 028import org.apache.commons.math3.util.MathArrays; 029 030/** 031 * This class is a re-implementation of {@link Rotation} using {@link RealFieldElement}. 032 * <p>Instance of this class are guaranteed to be immutable.</p> 033 * 034 * @param <T> the type of the field elements 035 * @see FieldVector3D 036 * @see RotationOrder 037 * @since 3.2 038 */ 039 040public class FieldRotation<T extends RealFieldElement<T>> implements Serializable { 041 042 /** Serializable version identifier */ 043 private static final long serialVersionUID = 20130224l; 044 045 /** Scalar coordinate of the quaternion. */ 046 private final T q0; 047 048 /** First coordinate of the vectorial part of the quaternion. */ 049 private final T q1; 050 051 /** Second coordinate of the vectorial part of the quaternion. */ 052 private final T q2; 053 054 /** Third coordinate of the vectorial part of the quaternion. */ 055 private final T q3; 056 057 /** Build a rotation from the quaternion coordinates. 058 * <p>A rotation can be built from a <em>normalized</em> quaternion, 059 * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> + 060 * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> + 061 * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized, 062 * the constructor can normalize it in a preprocessing step.</p> 063 * <p>Note that some conventions put the scalar part of the quaternion 064 * as the 4<sup>th</sup> component and the vector part as the first three 065 * components. This is <em>not</em> our convention. We put the scalar part 066 * as the first component.</p> 067 * @param q0 scalar part of the quaternion 068 * @param q1 first coordinate of the vectorial part of the quaternion 069 * @param q2 second coordinate of the vectorial part of the quaternion 070 * @param q3 third coordinate of the vectorial part of the quaternion 071 * @param needsNormalization if true, the coordinates are considered 072 * not to be normalized, a normalization preprocessing step is performed 073 * before using them 074 */ 075 public FieldRotation(final T q0, final T q1, final T q2, final T q3, final boolean needsNormalization) { 076 077 if (needsNormalization) { 078 // normalization preprocessing 079 final T inv = 080 q0.multiply(q0).add(q1.multiply(q1)).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().reciprocal(); 081 this.q0 = inv.multiply(q0); 082 this.q1 = inv.multiply(q1); 083 this.q2 = inv.multiply(q2); 084 this.q3 = inv.multiply(q3); 085 } else { 086 this.q0 = q0; 087 this.q1 = q1; 088 this.q2 = q2; 089 this.q3 = q3; 090 } 091 092 } 093 094 /** Build a rotation from an axis and an angle. 095 * <p>We use the convention that angles are oriented according to 096 * the effect of the rotation on vectors around the axis. That means 097 * that if (i, j, k) is a direct frame and if we first provide +k as 098 * the axis and π/2 as the angle to this constructor, and then 099 * {@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 θ about the unit vector (x, y, z) is the same as the 103 * rotation build from quaternion components { cos(-θ/2), 104 * x * sin(-θ/2), y * sin(-θ/2), z * sin(-θ/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 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 π/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 θ about the unit vector (x, y, z) is the same as the 132 * rotation build from quaternion components { cos(-θ/2), 133 * x * sin(-θ/2), y * sin(-θ/2), z * sin(-θ/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 */ 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 */ 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 (±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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 π, 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 */ 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 π) 528 * @see #FieldRotation(FieldVector3D, RealFieldElement) 529 */ 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 π + a<sub>1</sub>, π 546 * - a<sub>2</sub> and π + 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 -π/2 and π/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 π (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 * -π/2 or +π/2, for Euler angle singularities occur when the 566 * second angle is close to 0 or π, 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 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 π + a<sub>1</sub>, π 588 * - a<sub>2</sub> and π + 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 -π/2 and π/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 π (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 * -π/2 or +π/2, for Euler angle singularities occur when the 608 * second angle is close to 0 or π, 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 π. 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 */ 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}