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.math.geometry; 19 20 import java.io.Serializable; 21 22 import org.apache.commons.math.MathRuntimeException; 23 import org.apache.commons.math.exception.util.LocalizedFormats; 24 import org.apache.commons.math.util.FastMath; 25 26 /** 27 * This class implements rotations in a three-dimensional space. 28 * 29 * <p>Rotations can be represented by several different mathematical 30 * entities (matrices, axe and angle, Cardan or Euler angles, 31 * quaternions). This class presents an higher level abstraction, more 32 * user-oriented and hiding this implementation details. Well, for the 33 * curious, we use quaternions for the internal representation. The 34 * user can build a rotation from any of these representations, and 35 * any of these representations can be retrieved from a 36 * <code>Rotation</code> instance (see the various constructors and 37 * getters). In addition, a rotation can also be built implicitly 38 * from a set of vectors and their image.</p> 39 * <p>This implies that this class can be used to convert from one 40 * representation to another one. For example, converting a rotation 41 * matrix into a set of Cardan angles from can be done using the 42 * following single line of code:</p> 43 * <pre> 44 * double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ); 45 * </pre> 46 * <p>Focus is oriented on what a rotation <em>do</em> rather than on its 47 * underlying representation. Once it has been built, and regardless of its 48 * internal representation, a rotation is an <em>operator</em> which basically 49 * transforms three dimensional {@link Vector3D vectors} into other three 50 * dimensional {@link Vector3D vectors}. Depending on the application, the 51 * meaning of these vectors may vary and the semantics of the rotation also.</p> 52 * <p>For example in an spacecraft attitude simulation tool, users will often 53 * consider the vectors are fixed (say the Earth direction for example) and the 54 * frames change. The rotation transforms the coordinates of the vector in inertial 55 * frame into the coordinates of the same vector in satellite frame. In this 56 * case, the rotation implicitly defines the relation between the two frames.</p> 57 * <p>Another example could be a telescope control application, where the rotation 58 * would transform the sighting direction at rest into the desired observing 59 * direction when the telescope is pointed towards an object of interest. In this 60 * case the rotation transforms the direction at rest in a topocentric frame 61 * into the sighting direction in the same topocentric frame. This implies in this 62 * case the frame is fixed and the vector moves.</p> 63 * <p>In many case, both approaches will be combined. In our telescope example, 64 * we will probably also need to transform the observing direction in the topocentric 65 * frame into the observing direction in inertial frame taking into account the observatory 66 * location and the Earth rotation, which would essentially be an application of the 67 * first approach.</p> 68 * 69 * <p>These examples show that a rotation is what the user wants it to be. This 70 * class does not push the user towards one specific definition and hence does not 71 * provide methods like <code>projectVectorIntoDestinationFrame</code> or 72 * <code>computeTransformedDirection</code>. It provides simpler and more generic 73 * methods: {@link #applyTo(Vector3D) applyTo(Vector3D)} and {@link 74 * #applyInverseTo(Vector3D) applyInverseTo(Vector3D)}.</p> 75 * 76 * <p>Since a rotation is basically a vectorial operator, several rotations can be 77 * composed together and the composite operation <code>r = r<sub>1</sub> o 78 * r<sub>2</sub></code> (which means that for each vector <code>u</code>, 79 * <code>r(u) = r<sub>1</sub>(r<sub>2</sub>(u))</code>) is also a rotation. Hence 80 * we can consider that in addition to vectors, a rotation can be applied to other 81 * rotations as well (or to itself). With our previous notations, we would say we 82 * can apply <code>r<sub>1</sub></code> to <code>r<sub>2</sub></code> and the result 83 * we get is <code>r = r<sub>1</sub> o r<sub>2</sub></code>. For this purpose, the 84 * class provides the methods: {@link #applyTo(Rotation) applyTo(Rotation)} and 85 * {@link #applyInverseTo(Rotation) applyInverseTo(Rotation)}.</p> 86 * 87 * <p>Rotations are guaranteed to be immutable objects.</p> 88 * 89 * @version $Revision: 1067500 $ $Date: 2011-02-05 21:11:30 +0100 (sam. 05 févr. 2011) $ 90 * @see Vector3D 91 * @see RotationOrder 92 * @since 1.2 93 */ 94 95 public class Rotation implements Serializable { 96 97 /** Identity rotation. */ 98 public static final Rotation IDENTITY = new Rotation(1.0, 0.0, 0.0, 0.0, false); 99 100 /** Serializable version identifier */ 101 private static final long serialVersionUID = -2153622329907944313L; 102 103 /** Scalar coordinate of the quaternion. */ 104 private final double q0; 105 106 /** First coordinate of the vectorial part of the quaternion. */ 107 private final double q1; 108 109 /** Second coordinate of the vectorial part of the quaternion. */ 110 private final double q2; 111 112 /** Third coordinate of the vectorial part of the quaternion. */ 113 private final double q3; 114 115 /** Build a rotation from the quaternion coordinates. 116 * <p>A rotation can be built from a <em>normalized</em> quaternion, 117 * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> + 118 * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> + 119 * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized, 120 * the constructor can normalize it in a preprocessing step.</p> 121 * <p>Note that some conventions put the scalar part of the quaternion 122 * as the 4<sup>th</sup> component and the vector part as the first three 123 * components. This is <em>not</em> our convention. We put the scalar part 124 * as the first component.</p> 125 * @param q0 scalar part of the quaternion 126 * @param q1 first coordinate of the vectorial part of the quaternion 127 * @param q2 second coordinate of the vectorial part of the quaternion 128 * @param q3 third coordinate of the vectorial part of the quaternion 129 * @param needsNormalization if true, the coordinates are considered 130 * not to be normalized, a normalization preprocessing step is performed 131 * before using them 132 */ Rotation(double q0, double q1, double q2, double q3, boolean needsNormalization)133 public Rotation(double q0, double q1, double q2, double q3, 134 boolean needsNormalization) { 135 136 if (needsNormalization) { 137 // normalization preprocessing 138 double inv = 1.0 / FastMath.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); 139 q0 *= inv; 140 q1 *= inv; 141 q2 *= inv; 142 q3 *= inv; 143 } 144 145 this.q0 = q0; 146 this.q1 = q1; 147 this.q2 = q2; 148 this.q3 = q3; 149 150 } 151 152 /** Build a rotation from an axis and an angle. 153 * <p>We use the convention that angles are oriented according to 154 * the effect of the rotation on vectors around the axis. That means 155 * that if (i, j, k) is a direct frame and if we first provide +k as 156 * the axis and π/2 as the angle to this constructor, and then 157 * {@link #applyTo(Vector3D) apply} the instance to +i, we will get 158 * +j.</p> 159 * <p>Another way to represent our convention is to say that a rotation 160 * of angle θ about the unit vector (x, y, z) is the same as the 161 * rotation build from quaternion components { cos(-θ/2), 162 * x * sin(-θ/2), y * sin(-θ/2), z * sin(-θ/2) }. 163 * Note the minus sign on the angle!</p> 164 * <p>On the one hand this convention is consistent with a vectorial 165 * perspective (moving vectors in fixed frames), on the other hand it 166 * is different from conventions with a frame perspective (fixed vectors 167 * viewed from different frames) like the ones used for example in spacecraft 168 * attitude community or in the graphics community.</p> 169 * @param axis axis around which to rotate 170 * @param angle rotation angle. 171 * @exception ArithmeticException if the axis norm is zero 172 */ Rotation(Vector3D axis, double angle)173 public Rotation(Vector3D axis, double angle) { 174 175 double norm = axis.getNorm(); 176 if (norm == 0) { 177 throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS); 178 } 179 180 double halfAngle = -0.5 * angle; 181 double coeff = FastMath.sin(halfAngle) / norm; 182 183 q0 = FastMath.cos (halfAngle); 184 q1 = coeff * axis.getX(); 185 q2 = coeff * axis.getY(); 186 q3 = coeff * axis.getZ(); 187 188 } 189 190 /** Build a rotation from a 3X3 matrix. 191 192 * <p>Rotation matrices are orthogonal matrices, i.e. unit matrices 193 * (which are matrices for which m.m<sup>T</sup> = I) with real 194 * coefficients. The module of the determinant of unit matrices is 195 * 1, among the orthogonal 3X3 matrices, only the ones having a 196 * positive determinant (+1) are rotation matrices.</p> 197 * 198 * <p>When a rotation is defined by a matrix with truncated values 199 * (typically when it is extracted from a technical sheet where only 200 * four to five significant digits are available), the matrix is not 201 * orthogonal anymore. This constructor handles this case 202 * transparently by using a copy of the given matrix and applying a 203 * correction to the copy in order to perfect its orthogonality. If 204 * the Frobenius norm of the correction needed is above the given 205 * threshold, then the matrix is considered to be too far from a 206 * true rotation matrix and an exception is thrown.<p> 207 * 208 * @param m rotation matrix 209 * @param threshold convergence threshold for the iterative 210 * orthogonality correction (convergence is reached when the 211 * difference between two steps of the Frobenius norm of the 212 * correction is below this threshold) 213 * 214 * @exception NotARotationMatrixException if the matrix is not a 3X3 215 * matrix, or if it cannot be transformed into an orthogonal matrix 216 * with the given threshold, or if the determinant of the resulting 217 * orthogonal matrix is negative 218 * 219 */ Rotation(double[][] m, double threshold)220 public Rotation(double[][] m, double threshold) 221 throws NotARotationMatrixException { 222 223 // dimension check 224 if ((m.length != 3) || (m[0].length != 3) || 225 (m[1].length != 3) || (m[2].length != 3)) { 226 throw new NotARotationMatrixException( 227 LocalizedFormats.ROTATION_MATRIX_DIMENSIONS, 228 m.length, m[0].length); 229 } 230 231 // compute a "close" orthogonal matrix 232 double[][] ort = orthogonalizeMatrix(m, threshold); 233 234 // check the sign of the determinant 235 double det = ort[0][0] * (ort[1][1] * ort[2][2] - ort[2][1] * ort[1][2]) - 236 ort[1][0] * (ort[0][1] * ort[2][2] - ort[2][1] * ort[0][2]) + 237 ort[2][0] * (ort[0][1] * ort[1][2] - ort[1][1] * ort[0][2]); 238 if (det < 0.0) { 239 throw new NotARotationMatrixException( 240 LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT, 241 det); 242 } 243 244 // There are different ways to compute the quaternions elements 245 // from the matrix. They all involve computing one element from 246 // the diagonal of the matrix, and computing the three other ones 247 // using a formula involving a division by the first element, 248 // which unfortunately can be zero. Since the norm of the 249 // quaternion is 1, we know at least one element has an absolute 250 // value greater or equal to 0.5, so it is always possible to 251 // select the right formula and avoid division by zero and even 252 // numerical inaccuracy. Checking the elements in turn and using 253 // the first one greater than 0.45 is safe (this leads to a simple 254 // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) 255 double s = ort[0][0] + ort[1][1] + ort[2][2]; 256 if (s > -0.19) { 257 // compute q0 and deduce q1, q2 and q3 258 q0 = 0.5 * FastMath.sqrt(s + 1.0); 259 double inv = 0.25 / q0; 260 q1 = inv * (ort[1][2] - ort[2][1]); 261 q2 = inv * (ort[2][0] - ort[0][2]); 262 q3 = inv * (ort[0][1] - ort[1][0]); 263 } else { 264 s = ort[0][0] - ort[1][1] - ort[2][2]; 265 if (s > -0.19) { 266 // compute q1 and deduce q0, q2 and q3 267 q1 = 0.5 * FastMath.sqrt(s + 1.0); 268 double inv = 0.25 / q1; 269 q0 = inv * (ort[1][2] - ort[2][1]); 270 q2 = inv * (ort[0][1] + ort[1][0]); 271 q3 = inv * (ort[0][2] + ort[2][0]); 272 } else { 273 s = ort[1][1] - ort[0][0] - ort[2][2]; 274 if (s > -0.19) { 275 // compute q2 and deduce q0, q1 and q3 276 q2 = 0.5 * FastMath.sqrt(s + 1.0); 277 double inv = 0.25 / q2; 278 q0 = inv * (ort[2][0] - ort[0][2]); 279 q1 = inv * (ort[0][1] + ort[1][0]); 280 q3 = inv * (ort[2][1] + ort[1][2]); 281 } else { 282 // compute q3 and deduce q0, q1 and q2 283 s = ort[2][2] - ort[0][0] - ort[1][1]; 284 q3 = 0.5 * FastMath.sqrt(s + 1.0); 285 double inv = 0.25 / q3; 286 q0 = inv * (ort[0][1] - ort[1][0]); 287 q1 = inv * (ort[0][2] + ort[2][0]); 288 q2 = inv * (ort[2][1] + ort[1][2]); 289 } 290 } 291 } 292 293 } 294 295 /** Build the rotation that transforms a pair of vector into another pair. 296 297 * <p>Except for possible scale factors, if the instance were applied to 298 * the pair (u<sub>1</sub>, u<sub>2</sub>) it will produce the pair 299 * (v<sub>1</sub>, v<sub>2</sub>).</p> 300 * 301 * <p>If the angular separation between u<sub>1</sub> and u<sub>2</sub> is 302 * not the same as the angular separation between v<sub>1</sub> and 303 * v<sub>2</sub>, then a corrected v'<sub>2</sub> will be used rather than 304 * v<sub>2</sub>, the corrected vector will be in the (v<sub>1</sub>, 305 * v<sub>2</sub>) plane.</p> 306 * 307 * @param u1 first vector of the origin pair 308 * @param u2 second vector of the origin pair 309 * @param v1 desired image of u1 by the rotation 310 * @param v2 desired image of u2 by the rotation 311 * @exception IllegalArgumentException if the norm of one of the vectors is zero 312 */ Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2)313 public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2) { 314 315 // norms computation 316 double u1u1 = Vector3D.dotProduct(u1, u1); 317 double u2u2 = Vector3D.dotProduct(u2, u2); 318 double v1v1 = Vector3D.dotProduct(v1, v1); 319 double v2v2 = Vector3D.dotProduct(v2, v2); 320 if ((u1u1 == 0) || (u2u2 == 0) || (v1v1 == 0) || (v2v2 == 0)) { 321 throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR); 322 } 323 324 double u1x = u1.getX(); 325 double u1y = u1.getY(); 326 double u1z = u1.getZ(); 327 328 double u2x = u2.getX(); 329 double u2y = u2.getY(); 330 double u2z = u2.getZ(); 331 332 // normalize v1 in order to have (v1'|v1') = (u1|u1) 333 double coeff = FastMath.sqrt (u1u1 / v1v1); 334 double v1x = coeff * v1.getX(); 335 double v1y = coeff * v1.getY(); 336 double v1z = coeff * v1.getZ(); 337 v1 = new Vector3D(v1x, v1y, v1z); 338 339 // adjust v2 in order to have (u1|u2) = (v1|v2) and (v2'|v2') = (u2|u2) 340 double u1u2 = Vector3D.dotProduct(u1, u2); 341 double v1v2 = Vector3D.dotProduct(v1, v2); 342 double coeffU = u1u2 / u1u1; 343 double coeffV = v1v2 / u1u1; 344 double beta = FastMath.sqrt((u2u2 - u1u2 * coeffU) / (v2v2 - v1v2 * coeffV)); 345 double alpha = coeffU - beta * coeffV; 346 double v2x = alpha * v1x + beta * v2.getX(); 347 double v2y = alpha * v1y + beta * v2.getY(); 348 double v2z = alpha * v1z + beta * v2.getZ(); 349 v2 = new Vector3D(v2x, v2y, v2z); 350 351 // preliminary computation (we use explicit formulation instead 352 // of relying on the Vector3D class in order to avoid building lots 353 // of temporary objects) 354 Vector3D uRef = u1; 355 Vector3D vRef = v1; 356 double dx1 = v1x - u1.getX(); 357 double dy1 = v1y - u1.getY(); 358 double dz1 = v1z - u1.getZ(); 359 double dx2 = v2x - u2.getX(); 360 double dy2 = v2y - u2.getY(); 361 double dz2 = v2z - u2.getZ(); 362 Vector3D k = new Vector3D(dy1 * dz2 - dz1 * dy2, 363 dz1 * dx2 - dx1 * dz2, 364 dx1 * dy2 - dy1 * dx2); 365 double c = k.getX() * (u1y * u2z - u1z * u2y) + 366 k.getY() * (u1z * u2x - u1x * u2z) + 367 k.getZ() * (u1x * u2y - u1y * u2x); 368 369 if (c == 0) { 370 // the (q1, q2, q3) vector is in the (u1, u2) plane 371 // we try other vectors 372 Vector3D u3 = Vector3D.crossProduct(u1, u2); 373 Vector3D v3 = Vector3D.crossProduct(v1, v2); 374 double u3x = u3.getX(); 375 double u3y = u3.getY(); 376 double u3z = u3.getZ(); 377 double v3x = v3.getX(); 378 double v3y = v3.getY(); 379 double v3z = v3.getZ(); 380 381 double dx3 = v3x - u3x; 382 double dy3 = v3y - u3y; 383 double dz3 = v3z - u3z; 384 k = new Vector3D(dy1 * dz3 - dz1 * dy3, 385 dz1 * dx3 - dx1 * dz3, 386 dx1 * dy3 - dy1 * dx3); 387 c = k.getX() * (u1y * u3z - u1z * u3y) + 388 k.getY() * (u1z * u3x - u1x * u3z) + 389 k.getZ() * (u1x * u3y - u1y * u3x); 390 391 if (c == 0) { 392 // the (q1, q2, q3) vector is aligned with u1: 393 // we try (u2, u3) and (v2, v3) 394 k = new Vector3D(dy2 * dz3 - dz2 * dy3, 395 dz2 * dx3 - dx2 * dz3, 396 dx2 * dy3 - dy2 * dx3); 397 c = k.getX() * (u2y * u3z - u2z * u3y) + 398 k.getY() * (u2z * u3x - u2x * u3z) + 399 k.getZ() * (u2x * u3y - u2y * u3x); 400 401 if (c == 0) { 402 // the (q1, q2, q3) vector is aligned with everything 403 // this is really the identity rotation 404 q0 = 1.0; 405 q1 = 0.0; 406 q2 = 0.0; 407 q3 = 0.0; 408 return; 409 } 410 411 // we will have to use u2 and v2 to compute the scalar part 412 uRef = u2; 413 vRef = v2; 414 415 } 416 417 } 418 419 // compute the vectorial part 420 c = FastMath.sqrt(c); 421 double inv = 1.0 / (c + c); 422 q1 = inv * k.getX(); 423 q2 = inv * k.getY(); 424 q3 = inv * k.getZ(); 425 426 // compute the scalar part 427 k = new Vector3D(uRef.getY() * q3 - uRef.getZ() * q2, 428 uRef.getZ() * q1 - uRef.getX() * q3, 429 uRef.getX() * q2 - uRef.getY() * q1); 430 c = Vector3D.dotProduct(k, k); 431 q0 = Vector3D.dotProduct(vRef, k) / (c + c); 432 433 } 434 435 /** Build one of the rotations that transform one vector into another one. 436 437 * <p>Except for a possible scale factor, if the instance were 438 * applied to the vector u it will produce the vector v. There is an 439 * infinite number of such rotations, this constructor choose the 440 * one with the smallest associated angle (i.e. the one whose axis 441 * is orthogonal to the (u, v) plane). If u and v are colinear, an 442 * arbitrary rotation axis is chosen.</p> 443 * 444 * @param u origin vector 445 * @param v desired image of u by the rotation 446 * @exception IllegalArgumentException if the norm of one of the vectors is zero 447 */ Rotation(Vector3D u, Vector3D v)448 public Rotation(Vector3D u, Vector3D v) { 449 450 double normProduct = u.getNorm() * v.getNorm(); 451 if (normProduct == 0) { 452 throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR); 453 } 454 455 double dot = Vector3D.dotProduct(u, v); 456 457 if (dot < ((2.0e-15 - 1.0) * normProduct)) { 458 // special case u = -v: we select a PI angle rotation around 459 // an arbitrary vector orthogonal to u 460 Vector3D w = u.orthogonal(); 461 q0 = 0.0; 462 q1 = -w.getX(); 463 q2 = -w.getY(); 464 q3 = -w.getZ(); 465 } else { 466 // general case: (u, v) defines a plane, we select 467 // the shortest possible rotation: axis orthogonal to this plane 468 q0 = FastMath.sqrt(0.5 * (1.0 + dot / normProduct)); 469 double coeff = 1.0 / (2.0 * q0 * normProduct); 470 q1 = coeff * (v.getY() * u.getZ() - v.getZ() * u.getY()); 471 q2 = coeff * (v.getZ() * u.getX() - v.getX() * u.getZ()); 472 q3 = coeff * (v.getX() * u.getY() - v.getY() * u.getX()); 473 } 474 475 } 476 477 /** Build a rotation from three Cardan or Euler elementary rotations. 478 479 * <p>Cardan rotations are three successive rotations around the 480 * canonical axes X, Y and Z, each axis being used once. There are 481 * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler 482 * rotations are three successive rotations around the canonical 483 * axes X, Y and Z, the first and last rotations being around the 484 * same axis. There are 6 such sets of rotations (XYX, XZX, YXY, 485 * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p> 486 * <p>Beware that many people routinely use the term Euler angles even 487 * for what really are Cardan angles (this confusion is especially 488 * widespread in the aerospace business where Roll, Pitch and Yaw angles 489 * are often wrongly tagged as Euler angles).</p> 490 * 491 * @param order order of rotations to use 492 * @param alpha1 angle of the first elementary rotation 493 * @param alpha2 angle of the second elementary rotation 494 * @param alpha3 angle of the third elementary rotation 495 */ Rotation(RotationOrder order, double alpha1, double alpha2, double alpha3)496 public Rotation(RotationOrder order, 497 double alpha1, double alpha2, double alpha3) { 498 Rotation r1 = new Rotation(order.getA1(), alpha1); 499 Rotation r2 = new Rotation(order.getA2(), alpha2); 500 Rotation r3 = new Rotation(order.getA3(), alpha3); 501 Rotation composed = r1.applyTo(r2.applyTo(r3)); 502 q0 = composed.q0; 503 q1 = composed.q1; 504 q2 = composed.q2; 505 q3 = composed.q3; 506 } 507 508 /** Revert a rotation. 509 * Build a rotation which reverse the effect of another 510 * rotation. This means that if r(u) = v, then r.revert(v) = u. The 511 * instance is not changed. 512 * @return a new rotation whose effect is the reverse of the effect 513 * of the instance 514 */ revert()515 public Rotation revert() { 516 return new Rotation(-q0, q1, q2, q3, false); 517 } 518 519 /** Get the scalar coordinate of the quaternion. 520 * @return scalar coordinate of the quaternion 521 */ getQ0()522 public double getQ0() { 523 return q0; 524 } 525 526 /** Get the first coordinate of the vectorial part of the quaternion. 527 * @return first coordinate of the vectorial part of the quaternion 528 */ getQ1()529 public double getQ1() { 530 return q1; 531 } 532 533 /** Get the second coordinate of the vectorial part of the quaternion. 534 * @return second coordinate of the vectorial part of the quaternion 535 */ getQ2()536 public double getQ2() { 537 return q2; 538 } 539 540 /** Get the third coordinate of the vectorial part of the quaternion. 541 * @return third coordinate of the vectorial part of the quaternion 542 */ getQ3()543 public double getQ3() { 544 return q3; 545 } 546 547 /** Get the normalized axis of the rotation. 548 * @return normalized axis of the rotation 549 * @see #Rotation(Vector3D, double) 550 */ getAxis()551 public Vector3D getAxis() { 552 double squaredSine = q1 * q1 + q2 * q2 + q3 * q3; 553 if (squaredSine == 0) { 554 return new Vector3D(1, 0, 0); 555 } else if (q0 < 0) { 556 double inverse = 1 / FastMath.sqrt(squaredSine); 557 return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); 558 } 559 double inverse = -1 / FastMath.sqrt(squaredSine); 560 return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); 561 } 562 563 /** Get the angle of the rotation. 564 * @return angle of the rotation (between 0 and π) 565 * @see #Rotation(Vector3D, double) 566 */ getAngle()567 public double getAngle() { 568 if ((q0 < -0.1) || (q0 > 0.1)) { 569 return 2 * FastMath.asin(FastMath.sqrt(q1 * q1 + q2 * q2 + q3 * q3)); 570 } else if (q0 < 0) { 571 return 2 * FastMath.acos(-q0); 572 } 573 return 2 * FastMath.acos(q0); 574 } 575 576 /** Get the Cardan or Euler angles corresponding to the instance. 577 578 * <p>The equations show that each rotation can be defined by two 579 * different values of the Cardan or Euler angles set. For example 580 * if Cardan angles are used, the rotation defined by the angles 581 * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as 582 * the rotation defined by the angles π + a<sub>1</sub>, π 583 * - a<sub>2</sub> and π + a<sub>3</sub>. This method implements 584 * the following arbitrary choices:</p> 585 * <ul> 586 * <li>for Cardan angles, the chosen set is the one for which the 587 * second angle is between -π/2 and π/2 (i.e its cosine is 588 * positive),</li> 589 * <li>for Euler angles, the chosen set is the one for which the 590 * second angle is between 0 and π (i.e its sine is positive).</li> 591 * </ul> 592 * 593 * <p>Cardan and Euler angle have a very disappointing drawback: all 594 * of them have singularities. This means that if the instance is 595 * too close to the singularities corresponding to the given 596 * rotation order, it will be impossible to retrieve the angles. For 597 * Cardan angles, this is often called gimbal lock. There is 598 * <em>nothing</em> to do to prevent this, it is an intrinsic problem 599 * with Cardan and Euler representation (but not a problem with the 600 * rotation itself, which is perfectly well defined). For Cardan 601 * angles, singularities occur when the second angle is close to 602 * -π/2 or +π/2, for Euler angle singularities occur when the 603 * second angle is close to 0 or π, this implies that the identity 604 * rotation is always singular for Euler angles!</p> 605 * 606 * @param order rotation order to use 607 * @return an array of three angles, in the order specified by the set 608 * @exception CardanEulerSingularityException if the rotation is 609 * singular with respect to the angles set specified 610 */ getAngles(RotationOrder order)611 public double[] getAngles(RotationOrder order) 612 throws CardanEulerSingularityException { 613 614 if (order == RotationOrder.XYZ) { 615 616 // r (Vector3D.plusK) coordinates are : 617 // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) 618 // (-r) (Vector3D.plusI) coordinates are : 619 // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) 620 // and we can choose to have theta in the interval [-PI/2 ; +PI/2] 621 Vector3D v1 = applyTo(Vector3D.PLUS_K); 622 Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); 623 if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { 624 throw new CardanEulerSingularityException(true); 625 } 626 return new double[] { 627 FastMath.atan2(-(v1.getY()), v1.getZ()), 628 FastMath.asin(v2.getZ()), 629 FastMath.atan2(-(v2.getY()), v2.getX()) 630 }; 631 632 } else if (order == RotationOrder.XZY) { 633 634 // r (Vector3D.plusJ) coordinates are : 635 // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) 636 // (-r) (Vector3D.plusI) coordinates are : 637 // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) 638 // and we can choose to have psi in the interval [-PI/2 ; +PI/2] 639 Vector3D v1 = applyTo(Vector3D.PLUS_J); 640 Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); 641 if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { 642 throw new CardanEulerSingularityException(true); 643 } 644 return new double[] { 645 FastMath.atan2(v1.getZ(), v1.getY()), 646 -FastMath.asin(v2.getY()), 647 FastMath.atan2(v2.getZ(), v2.getX()) 648 }; 649 650 } else if (order == RotationOrder.YXZ) { 651 652 // r (Vector3D.plusK) coordinates are : 653 // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) 654 // (-r) (Vector3D.plusJ) coordinates are : 655 // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) 656 // and we can choose to have phi in the interval [-PI/2 ; +PI/2] 657 Vector3D v1 = applyTo(Vector3D.PLUS_K); 658 Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); 659 if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { 660 throw new CardanEulerSingularityException(true); 661 } 662 return new double[] { 663 FastMath.atan2(v1.getX(), v1.getZ()), 664 -FastMath.asin(v2.getZ()), 665 FastMath.atan2(v2.getX(), v2.getY()) 666 }; 667 668 } else if (order == RotationOrder.YZX) { 669 670 // r (Vector3D.plusI) coordinates are : 671 // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) 672 // (-r) (Vector3D.plusJ) coordinates are : 673 // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) 674 // and we can choose to have psi in the interval [-PI/2 ; +PI/2] 675 Vector3D v1 = applyTo(Vector3D.PLUS_I); 676 Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); 677 if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { 678 throw new CardanEulerSingularityException(true); 679 } 680 return new double[] { 681 FastMath.atan2(-(v1.getZ()), v1.getX()), 682 FastMath.asin(v2.getX()), 683 FastMath.atan2(-(v2.getZ()), v2.getY()) 684 }; 685 686 } else if (order == RotationOrder.ZXY) { 687 688 // r (Vector3D.plusJ) coordinates are : 689 // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) 690 // (-r) (Vector3D.plusK) 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 Vector3D v1 = applyTo(Vector3D.PLUS_J); 694 Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); 695 if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { 696 throw new CardanEulerSingularityException(true); 697 } 698 return new double[] { 699 FastMath.atan2(-(v1.getX()), v1.getY()), 700 FastMath.asin(v2.getY()), 701 FastMath.atan2(-(v2.getX()), v2.getZ()) 702 }; 703 704 } else if (order == RotationOrder.ZYX) { 705 706 // r (Vector3D.plusI) coordinates are : 707 // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) 708 // (-r) (Vector3D.plusK) coordinates are : 709 // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) 710 // and we can choose to have theta in the interval [-PI/2 ; +PI/2] 711 Vector3D v1 = applyTo(Vector3D.PLUS_I); 712 Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); 713 if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { 714 throw new CardanEulerSingularityException(true); 715 } 716 return new double[] { 717 FastMath.atan2(v1.getY(), v1.getX()), 718 -FastMath.asin(v2.getX()), 719 FastMath.atan2(v2.getY(), v2.getZ()) 720 }; 721 722 } else if (order == RotationOrder.XYX) { 723 724 // r (Vector3D.plusI) coordinates are : 725 // cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta) 726 // (-r) (Vector3D.plusI) coordinates are : 727 // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2) 728 // and we can choose to have theta in the interval [0 ; PI] 729 Vector3D v1 = applyTo(Vector3D.PLUS_I); 730 Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); 731 if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { 732 throw new CardanEulerSingularityException(false); 733 } 734 return new double[] { 735 FastMath.atan2(v1.getY(), -v1.getZ()), 736 FastMath.acos(v2.getX()), 737 FastMath.atan2(v2.getY(), v2.getZ()) 738 }; 739 740 } else if (order == RotationOrder.XZX) { 741 742 // r (Vector3D.plusI) coordinates are : 743 // cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi) 744 // (-r) (Vector3D.plusI) coordinates are : 745 // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2) 746 // and we can choose to have psi in the interval [0 ; PI] 747 Vector3D v1 = applyTo(Vector3D.PLUS_I); 748 Vector3D v2 = applyInverseTo(Vector3D.PLUS_I); 749 if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { 750 throw new CardanEulerSingularityException(false); 751 } 752 return new double[] { 753 FastMath.atan2(v1.getZ(), v1.getY()), 754 FastMath.acos(v2.getX()), 755 FastMath.atan2(v2.getZ(), -v2.getY()) 756 }; 757 758 } else if (order == RotationOrder.YXY) { 759 760 // r (Vector3D.plusJ) coordinates are : 761 // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) 762 // (-r) (Vector3D.plusJ) coordinates are : 763 // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) 764 // and we can choose to have phi in the interval [0 ; PI] 765 Vector3D v1 = applyTo(Vector3D.PLUS_J); 766 Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); 767 if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { 768 throw new CardanEulerSingularityException(false); 769 } 770 return new double[] { 771 FastMath.atan2(v1.getX(), v1.getZ()), 772 FastMath.acos(v2.getY()), 773 FastMath.atan2(v2.getX(), -v2.getZ()) 774 }; 775 776 } else if (order == RotationOrder.YZY) { 777 778 // r (Vector3D.plusJ) coordinates are : 779 // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) 780 // (-r) (Vector3D.plusJ) coordinates are : 781 // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) 782 // and we can choose to have psi in the interval [0 ; PI] 783 Vector3D v1 = applyTo(Vector3D.PLUS_J); 784 Vector3D v2 = applyInverseTo(Vector3D.PLUS_J); 785 if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { 786 throw new CardanEulerSingularityException(false); 787 } 788 return new double[] { 789 FastMath.atan2(v1.getZ(), -v1.getX()), 790 FastMath.acos(v2.getY()), 791 FastMath.atan2(v2.getZ(), v2.getX()) 792 }; 793 794 } else if (order == RotationOrder.ZXZ) { 795 796 // r (Vector3D.plusK) coordinates are : 797 // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) 798 // (-r) (Vector3D.plusK) coordinates are : 799 // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) 800 // and we can choose to have phi in the interval [0 ; PI] 801 Vector3D v1 = applyTo(Vector3D.PLUS_K); 802 Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); 803 if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { 804 throw new CardanEulerSingularityException(false); 805 } 806 return new double[] { 807 FastMath.atan2(v1.getX(), -v1.getY()), 808 FastMath.acos(v2.getZ()), 809 FastMath.atan2(v2.getX(), v2.getY()) 810 }; 811 812 } else { // last possibility is ZYZ 813 814 // r (Vector3D.plusK) coordinates are : 815 // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) 816 // (-r) (Vector3D.plusK) coordinates are : 817 // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) 818 // and we can choose to have theta in the interval [0 ; PI] 819 Vector3D v1 = applyTo(Vector3D.PLUS_K); 820 Vector3D v2 = applyInverseTo(Vector3D.PLUS_K); 821 if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { 822 throw new CardanEulerSingularityException(false); 823 } 824 return new double[] { 825 FastMath.atan2(v1.getY(), v1.getX()), 826 FastMath.acos(v2.getZ()), 827 FastMath.atan2(v2.getY(), -v2.getX()) 828 }; 829 830 } 831 832 } 833 834 /** Get the 3X3 matrix corresponding to the instance 835 * @return the matrix corresponding to the instance 836 */ getMatrix()837 public double[][] getMatrix() { 838 839 // products 840 double q0q0 = q0 * q0; 841 double q0q1 = q0 * q1; 842 double q0q2 = q0 * q2; 843 double q0q3 = q0 * q3; 844 double q1q1 = q1 * q1; 845 double q1q2 = q1 * q2; 846 double q1q3 = q1 * q3; 847 double q2q2 = q2 * q2; 848 double q2q3 = q2 * q3; 849 double q3q3 = q3 * q3; 850 851 // create the matrix 852 double[][] m = new double[3][]; 853 m[0] = new double[3]; 854 m[1] = new double[3]; 855 m[2] = new double[3]; 856 857 m [0][0] = 2.0 * (q0q0 + q1q1) - 1.0; 858 m [1][0] = 2.0 * (q1q2 - q0q3); 859 m [2][0] = 2.0 * (q1q3 + q0q2); 860 861 m [0][1] = 2.0 * (q1q2 + q0q3); 862 m [1][1] = 2.0 * (q0q0 + q2q2) - 1.0; 863 m [2][1] = 2.0 * (q2q3 - q0q1); 864 865 m [0][2] = 2.0 * (q1q3 - q0q2); 866 m [1][2] = 2.0 * (q2q3 + q0q1); 867 m [2][2] = 2.0 * (q0q0 + q3q3) - 1.0; 868 869 return m; 870 871 } 872 873 /** Apply the rotation to a vector. 874 * @param u vector to apply the rotation to 875 * @return a new vector which is the image of u by the rotation 876 */ applyTo(Vector3D u)877 public Vector3D applyTo(Vector3D u) { 878 879 double x = u.getX(); 880 double y = u.getY(); 881 double z = u.getZ(); 882 883 double s = q1 * x + q2 * y + q3 * z; 884 885 return new Vector3D(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x, 886 2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y, 887 2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z); 888 889 } 890 891 /** Apply the inverse of the rotation to a vector. 892 * @param u vector to apply the inverse of the rotation to 893 * @return a new vector which such that u is its image by the rotation 894 */ applyInverseTo(Vector3D u)895 public Vector3D applyInverseTo(Vector3D u) { 896 897 double x = u.getX(); 898 double y = u.getY(); 899 double z = u.getZ(); 900 901 double s = q1 * x + q2 * y + q3 * z; 902 double m0 = -q0; 903 904 return new Vector3D(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x, 905 2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y, 906 2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z); 907 908 } 909 910 /** Apply the instance to another rotation. 911 * Applying the instance to a rotation is computing the composition 912 * in an order compliant with the following rule : let u be any 913 * vector and v its image by r (i.e. r.applyTo(u) = v), let w be the image 914 * of v by the instance (i.e. applyTo(v) = w), then w = comp.applyTo(u), 915 * where comp = applyTo(r). 916 * @param r rotation to apply the rotation to 917 * @return a new rotation which is the composition of r by the instance 918 */ applyTo(Rotation r)919 public Rotation applyTo(Rotation r) { 920 return new Rotation(r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), 921 r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), 922 r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), 923 r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1), 924 false); 925 } 926 927 /** Apply the inverse of the instance to another rotation. 928 * Applying the inverse of the instance to a rotation is computing 929 * the composition in an order compliant with the following rule : 930 * let u be any vector and v its image by r (i.e. r.applyTo(u) = v), 931 * let w be the inverse image of v by the instance 932 * (i.e. applyInverseTo(v) = w), then w = comp.applyTo(u), where 933 * comp = applyInverseTo(r). 934 * @param r rotation to apply the rotation to 935 * @return a new rotation which is the composition of r by the inverse 936 * of the instance 937 */ applyInverseTo(Rotation r)938 public Rotation applyInverseTo(Rotation r) { 939 return new Rotation(-r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), 940 -r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), 941 -r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), 942 -r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1), 943 false); 944 } 945 946 /** Perfect orthogonality on a 3X3 matrix. 947 * @param m initial matrix (not exactly orthogonal) 948 * @param threshold convergence threshold for the iterative 949 * orthogonality correction (convergence is reached when the 950 * difference between two steps of the Frobenius norm of the 951 * correction is below this threshold) 952 * @return an orthogonal matrix close to m 953 * @exception NotARotationMatrixException if the matrix cannot be 954 * orthogonalized with the given threshold after 10 iterations 955 */ orthogonalizeMatrix(double[][] m, double threshold)956 private double[][] orthogonalizeMatrix(double[][] m, double threshold) 957 throws NotARotationMatrixException { 958 double[] m0 = m[0]; 959 double[] m1 = m[1]; 960 double[] m2 = m[2]; 961 double x00 = m0[0]; 962 double x01 = m0[1]; 963 double x02 = m0[2]; 964 double x10 = m1[0]; 965 double x11 = m1[1]; 966 double x12 = m1[2]; 967 double x20 = m2[0]; 968 double x21 = m2[1]; 969 double x22 = m2[2]; 970 double fn = 0; 971 double fn1; 972 973 double[][] o = new double[3][3]; 974 double[] o0 = o[0]; 975 double[] o1 = o[1]; 976 double[] o2 = o[2]; 977 978 // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M) 979 int i = 0; 980 while (++i < 11) { 981 982 // Mt.Xn 983 double mx00 = m0[0] * x00 + m1[0] * x10 + m2[0] * x20; 984 double mx10 = m0[1] * x00 + m1[1] * x10 + m2[1] * x20; 985 double mx20 = m0[2] * x00 + m1[2] * x10 + m2[2] * x20; 986 double mx01 = m0[0] * x01 + m1[0] * x11 + m2[0] * x21; 987 double mx11 = m0[1] * x01 + m1[1] * x11 + m2[1] * x21; 988 double mx21 = m0[2] * x01 + m1[2] * x11 + m2[2] * x21; 989 double mx02 = m0[0] * x02 + m1[0] * x12 + m2[0] * x22; 990 double mx12 = m0[1] * x02 + m1[1] * x12 + m2[1] * x22; 991 double mx22 = m0[2] * x02 + m1[2] * x12 + m2[2] * x22; 992 993 // Xn+1 994 o0[0] = x00 - 0.5 * (x00 * mx00 + x01 * mx10 + x02 * mx20 - m0[0]); 995 o0[1] = x01 - 0.5 * (x00 * mx01 + x01 * mx11 + x02 * mx21 - m0[1]); 996 o0[2] = x02 - 0.5 * (x00 * mx02 + x01 * mx12 + x02 * mx22 - m0[2]); 997 o1[0] = x10 - 0.5 * (x10 * mx00 + x11 * mx10 + x12 * mx20 - m1[0]); 998 o1[1] = x11 - 0.5 * (x10 * mx01 + x11 * mx11 + x12 * mx21 - m1[1]); 999 o1[2] = x12 - 0.5 * (x10 * mx02 + x11 * mx12 + x12 * mx22 - m1[2]); 1000 o2[0] = x20 - 0.5 * (x20 * mx00 + x21 * mx10 + x22 * mx20 - m2[0]); 1001 o2[1] = x21 - 0.5 * (x20 * mx01 + x21 * mx11 + x22 * mx21 - m2[1]); 1002 o2[2] = x22 - 0.5 * (x20 * mx02 + x21 * mx12 + x22 * mx22 - m2[2]); 1003 1004 // correction on each elements 1005 double corr00 = o0[0] - m0[0]; 1006 double corr01 = o0[1] - m0[1]; 1007 double corr02 = o0[2] - m0[2]; 1008 double corr10 = o1[0] - m1[0]; 1009 double corr11 = o1[1] - m1[1]; 1010 double corr12 = o1[2] - m1[2]; 1011 double corr20 = o2[0] - m2[0]; 1012 double corr21 = o2[1] - m2[1]; 1013 double corr22 = o2[2] - m2[2]; 1014 1015 // Frobenius norm of the correction 1016 fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 + 1017 corr10 * corr10 + corr11 * corr11 + corr12 * corr12 + 1018 corr20 * corr20 + corr21 * corr21 + corr22 * corr22; 1019 1020 // convergence test 1021 if (FastMath.abs(fn1 - fn) <= threshold) 1022 return o; 1023 1024 // prepare next iteration 1025 x00 = o0[0]; 1026 x01 = o0[1]; 1027 x02 = o0[2]; 1028 x10 = o1[0]; 1029 x11 = o1[1]; 1030 x12 = o1[2]; 1031 x20 = o2[0]; 1032 x21 = o2[1]; 1033 x22 = o2[2]; 1034 fn = fn1; 1035 1036 } 1037 1038 // the algorithm did not converge after 10 iterations 1039 throw new NotARotationMatrixException( 1040 LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX, 1041 i - 1); 1042 } 1043 1044 /** Compute the <i>distance</i> between two rotations. 1045 * <p>The <i>distance</i> is intended here as a way to check if two 1046 * rotations are almost similar (i.e. they transform vectors the same way) 1047 * or very different. It is mathematically defined as the angle of 1048 * the rotation r that prepended to one of the rotations gives the other 1049 * one:</p> 1050 * <pre> 1051 * r<sub>1</sub>(r) = r<sub>2</sub> 1052 * </pre> 1053 * <p>This distance is an angle between 0 and π. Its value is the smallest 1054 * possible upper bound of the angle in radians between r<sub>1</sub>(v) 1055 * and r<sub>2</sub>(v) for all possible vectors v. This upper bound is 1056 * reached for some v. The distance is equal to 0 if and only if the two 1057 * rotations are identical.</p> 1058 * <p>Comparing two rotations should always be done using this value rather 1059 * than for example comparing the components of the quaternions. It is much 1060 * more stable, and has a geometric meaning. Also comparing quaternions 1061 * components is error prone since for example quaternions (0.36, 0.48, -0.48, -0.64) 1062 * and (-0.36, -0.48, 0.48, 0.64) represent exactly the same rotation despite 1063 * their components are different (they are exact opposites).</p> 1064 * @param r1 first rotation 1065 * @param r2 second rotation 1066 * @return <i>distance</i> between r1 and r2 1067 */ distance(Rotation r1, Rotation r2)1068 public static double distance(Rotation r1, Rotation r2) { 1069 return r1.applyInverseTo(r2).getAngle(); 1070 } 1071 1072 } 1073