📄 rotation.java
字号:
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.apache.commons.math.geometry;import java.io.Serializable;/** * This class implements rotations in a three-dimensional space. * * <p>Rotations can be represented by several different mathematical * entities (matrices, axe and angle, Cardan or Euler angles, * quaternions). This class presents an higher level abstraction, more * user-oriented and hiding this implementation details. Well, for the * curious, we use quaternions for the internal representation. The * user can build a rotation from any of these representations, and * any of these representations can be retrieved from a * <code>Rotation</code> instance (see the various constructors and * getters). In addition, a rotation can also be built implicitely * from a set of vectors and their image.</p> * <p>This implies that this class can be used to convert from one * representation to another one. For example, converting a rotation * matrix into a set of Cardan angles from can be done using the * followong single line of code:</p> * <pre> * double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ); * </pre> * <p>Focus is oriented on what a rotation <em>do</em> rather than on its * underlying representation. Once it has been built, and regardless of its * internal representation, a rotation is an <em>operator</em> which basically * transforms three dimensional {@link Vector3D vectors} into other three * dimensional {@link Vector3D vectors}. Depending on the application, the * meaning of these vectors may vary and the semantics of the rotation also.</p> * <p>For example in an spacecraft attitude simulation tool, users will often * consider the vectors are fixed (say the Earth direction for example) and the * rotation transforms the coordinates coordinates of this vector in inertial * frame into the coordinates of the same vector in satellite frame. In this * case, the rotation implicitely defines the relation between the two frames. * Another example could be a telescope control application, where the rotation * would transform the sighting direction at rest into the desired observing * direction when the telescope is pointed towards an object of interest. In this * case the rotation transforms the directionf at rest in a topocentric frame * into the sighting direction in the same topocentric frame. In many case, both * approaches will be combined, in our telescope example, we will probably also * need to transform the observing direction in the topocentric frame into the * observing direction in inertial frame taking into account the observatory * location and the Earth rotation.</p> * * <p>These examples show that a rotation is what the user wants it to be, so this * class does not push the user towards one specific definition and hence does not * provide methods like <code>projectVectorIntoDestinationFrame</code> or * <code>computeTransformedDirection</code>. It provides simpler and more generic * methods: {@link #applyTo(Vector3D) applyTo(Vector3D)} and {@link * #applyInverseTo(Vector3D) applyInverseTo(Vector3D)}.</p> * * <p>Since a rotation is basically a vectorial operator, several rotations can be * composed together and the composite operation <code>r = r<sub>1</sub> o * r<sub>2</sub></code> (which means that for each vector <code>u</code>, * <code>r(u) = r<sub>1</sub>(r<sub>2</sub>(u))</code>) is also a rotation. Hence * we can consider that in addition to vectors, a rotation can be applied to other * rotations as well (or to itself). With our previous notations, we would say we * can apply <code>r<sub>1</sub></code> to <code>r<sub>2</sub></code> and the result * we get is <code>r = r<sub>1</sub> o r<sub>2</sub></code>. For this purpose, the * class provides the methods: {@link #applyTo(Rotation) applyTo(Rotation)} and * {@link #applyInverseTo(Rotation) applyInverseTo(Rotation)}.</p> * * <p>Rotations are guaranteed to be immutable objects.</p> * * @version $Revision: 627994 $ $Date: 2008-02-15 03:16:05 -0700 (Fri, 15 Feb 2008) $ * @see Vector3D * @see RotationOrder * @since 1.2 */public class Rotation implements Serializable { /** Build the identity rotation. */ public Rotation() { q0 = 1; q1 = 0; q2 = 0; q3 = 0; } /** Build a rotation from the quaternion coordinates. * <p>A rotation can be built from a <em>normalized</em> quaternion, * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> + * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> + * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized, * the constructor can normalize it in a preprocessing step.</p> * @param q0 scalar part of the quaternion * @param q1 first coordinate of the vectorial part of the quaternion * @param q2 second coordinate of the vectorial part of the quaternion * @param q3 third coordinate of the vectorial part of the quaternion * @param needsNormalization if true, the coordinates are considered * not to be normalized, a normalization preprocessing step is performed * before using them */ public Rotation(double q0, double q1, double q2, double q3, boolean needsNormalization) { if (needsNormalization) { // normalization preprocessing double inv = 1.0 / Math.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= inv; q1 *= inv; q2 *= inv; q3 *= inv; } this.q0 = q0; this.q1 = q1; this.q2 = q2; this.q3 = q3; } /** Build a rotation from an axis and an angle. * <p>We use the convention that angles are oriented according to * the effect of the rotation on vectors around the axis. That means * that if (i, j, k) is a direct frame and if we first provide +k as * the axis and PI/2 as the angle to this constructor, and then * {@link #applyTo(Vector3D) apply} the instance to +i, we will get * +j.</p> * @param axis axis around which to rotate * @param angle rotation angle. * @exception ArithmeticException if the axis norm is zero */ public Rotation(Vector3D axis, double angle) { double norm = axis.getNorm(); if (norm == 0) { throw new ArithmeticException("zero norm for rotation axis"); } double halfAngle = -0.5 * angle; double coeff = Math.sin(halfAngle) / norm; q0 = Math.cos (halfAngle); q1 = coeff * axis.getX(); q2 = coeff * axis.getY(); q3 = coeff * axis.getZ(); } /** Build a rotation from a 3X3 matrix. * <p>Rotation matrices are orthogonal matrices, i.e. unit matrices * (which are matrices for which m.m<sup>T</sup> = I) with real * coefficients. The module of the determinant of unit matrices is * 1, among the orthogonal 3X3 matrices, only the ones having a * positive determinant (+1) are rotation matrices.</p> * <p>When a rotation is defined by a matrix with truncated values * (typically when it is extracted from a technical sheet where only * four to five significant digits are available), the matrix is not * orthogonal anymore. This constructor handles this case * transparently by using a copy of the given matrix and applying a * correction to the copy in order to perfect its orthogonality. If * the Frobenius norm of the correction needed is above the given * threshold, then the matrix is considered to be too far from a * true rotation matrix and an exception is thrown.<p> * @param m rotation matrix * @param threshold convergence threshold for the iterative * orthogonality correction (convergence is reached when the * difference between two steps of the Frobenius norm of the * correction is below this threshold) * @exception NotARotationMatrixException if the matrix is not a 3X3 * matrix, or if it cannot be transformed into an orthogonal matrix * with the given threshold, or if the determinant of the resulting * orthogonal matrix is negative */ public Rotation(double[][] m, double threshold) throws NotARotationMatrixException { // dimension check if ((m.length != 3) || (m[0].length != 3) || (m[1].length != 3) || (m[2].length != 3)) { throw new NotARotationMatrixException("a {0}x{1} matrix" + " cannot be a rotation matrix", new Object[] { Integer.toString(m.length), Integer.toString(m[0].length) }); } // compute a "close" orthogonal matrix double[][] ort = orthogonalizeMatrix(m, threshold); // check the sign of the determinant double det = ort[0][0] * (ort[1][1] * ort[2][2] - ort[2][1] * ort[1][2]) - ort[1][0] * (ort[0][1] * ort[2][2] - ort[2][1] * ort[0][2]) + ort[2][0] * (ort[0][1] * ort[1][2] - ort[1][1] * ort[0][2]); if (det < 0.0) { throw new NotARotationMatrixException("the closest orthogonal matrix" + " has a negative determinant {0}", new Object[] { Double.toString(det) }); } // There are different ways to compute the quaternions elements // from the matrix. They all involve computing one element from // the diagonal of the matrix, and computing the three other ones // using a formula involving a division by the first element, // which unfortunately can be zero. Since the norm of the // quaternion is 1, we know at least one element has an absolute // value greater or equal to 0.5, so it is always possible to // select the right formula and avoid division by zero and even // numerical inaccuracy. Checking the elements in turn and using // the first one greater than 0.45 is safe (this leads to a simple // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) double s = ort[0][0] + ort[1][1] + ort[2][2]; if (s > -0.19) { // compute q0 and deduce q1, q2 and q3 q0 = 0.5 * Math.sqrt(s + 1.0); double inv = 0.25 / q0; q1 = inv * (ort[1][2] - ort[2][1]); q2 = inv * (ort[2][0] - ort[0][2]); q3 = inv * (ort[0][1] - ort[1][0]); } else { s = ort[0][0] - ort[1][1] - ort[2][2]; if (s > -0.19) { // compute q1 and deduce q0, q2 and q3 q1 = 0.5 * Math.sqrt(s + 1.0); double inv = 0.25 / q1; q0 = inv * (ort[1][2] - ort[2][1]); q2 = inv * (ort[0][1] + ort[1][0]); q3 = inv * (ort[0][2] + ort[2][0]); } else { s = ort[1][1] - ort[0][0] - ort[2][2]; if (s > -0.19) { // compute q2 and deduce q0, q1 and q3 q2 = 0.5 * Math.sqrt(s + 1.0); double inv = 0.25 / q2; q0 = inv * (ort[2][0] - ort[0][2]); q1 = inv * (ort[0][1] + ort[1][0]); q3 = inv * (ort[2][1] + ort[1][2]); } else { // compute q3 and deduce q0, q1 and q2 s = ort[2][2] - ort[0][0] - ort[1][1]; q3 = 0.5 * Math.sqrt(s + 1.0); double inv = 0.25 / q3; q0 = inv * (ort[0][1] - ort[1][0]); q1 = inv * (ort[0][2] + ort[2][0]); q2 = inv * (ort[2][1] + ort[1][2]); } } } } /** Build the rotation that transforms a pair of vector into another pair. * <p>Except for possible scale factors, if the instance were applied to * the pair (u<sub>1</sub>, u<sub>2</sub>) it will produce the pair * (v<sub>1</sub>, v<sub>2</sub>).</p> * <p>If the angular separation between u<sub>1</sub> and u<sub>2</sub> is * not the same as the angular separation between v<sub>1</sub> and * v<sub>2</sub>, then a corrected v'<sub>2</sub> will be used rather than * v<sub>2</sub>, the corrected vector will be in the (v<sub>1</sub>, * v<sub>2</sub>) plane.</p> * @param u1 first vector of the origin pair * @param u2 second vector of the origin pair * @param v1 desired image of u1 by the rotation * @param v2 desired image of u2 by the rotation * @exception IllegalArgumentException if the norm of one of the vectors is zero */ public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2) { // norms computation double u1u1 = Vector3D.dotProduct(u1, u1); double u2u2 = Vector3D.dotProduct(u2, u2); double v1v1 = Vector3D.dotProduct(v1, v1); double v2v2 = Vector3D.dotProduct(v2, v2); if ((u1u1 == 0) || (u2u2 == 0) || (v1v1 == 0) || (v2v2 == 0)) { throw new IllegalArgumentException("zero norm for rotation defining vector"); } double u1x = u1.getX(); double u1y = u1.getY(); double u1z = u1.getZ(); double u2x = u2.getX(); double u2y = u2.getY(); double u2z = u2.getZ(); // normalize v1 in order to have (v1'|v1') = (u1|u1) double coeff = Math.sqrt (u1u1 / v1v1); double v1x = coeff * v1.getX(); double v1y = coeff * v1.getY(); double v1z = coeff * v1.getZ(); v1 = new Vector3D(v1x, v1y, v1z); // adjust v2 in order to have (u1|u2) = (v1|v2) and (v2'|v2') = (u2|u2) double u1u2 = Vector3D.dotProduct(u1, u2); double v1v2 = Vector3D.dotProduct(v1, v2); double coeffU = u1u2 / u1u1; double coeffV = v1v2 / u1u1; double beta = Math.sqrt((u2u2 - u1u2 * coeffU) / (v2v2 - v1v2 * coeffV)); double alpha = coeffU - beta * coeffV; double v2x = alpha * v1x + beta * v2.getX(); double v2y = alpha * v1y + beta * v2.getY(); double v2z = alpha * v1z + beta * v2.getZ(); v2 = new Vector3D(v2x, v2y, v2z); // preliminary computation (we use explicit formulation instead // of relying on the Vector3D class in order to avoid building lots // of temporary objects) Vector3D uRef = u1; Vector3D vRef = v1; double dx1 = v1x - u1.getX(); double dy1 = v1y - u1.getY(); double dz1 = v1z - u1.getZ(); double dx2 = v2x - u2.getX(); double dy2 = v2y - u2.getY(); double dz2 = v2z - u2.getZ(); Vector3D k = new Vector3D(dy1 * dz2 - dz1 * dy2, dz1 * dx2 - dx1 * dz2, dx1 * dy2 - dy1 * dx2); double c = k.getX() * (u1y * u2z - u1z * u2y) + k.getY() * (u1z * u2x - u1x * u2z) + k.getZ() * (u1x * u2y - u1y * u2x); if (c == 0) { // the (q1, q2, q3) vector is in the (u1, u2) plane
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -