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 package org.apache.commons.geometry.euclidean.threed.rotation;
18
19 import java.util.Arrays;
20 import java.util.Objects;
21
22 /** <p>
23 * Class representing a sequence of axis-angle rotations. These types of
24 * rotations are commonly called <em>Euler angles</em>, <em>Tait-Bryan angles</em>,
25 * or <em>Cardan angles</em> depending on the properties of the rotation sequence and
26 * the particular use case. A sequence of three rotations around at least two different
27 * axes is sufficient to represent any rotation or orientation in 3 dimensional space.
28 * However, in order to unambiguously represent the rotation, the following information
29 * must be provided along with the rotation angles:
30 * <ul>
31 * <li><strong>Axis sequence</strong> - The axes that the rotation angles are associated with and
32 * in what order they occur.
33 * </li>
34 * <li><strong>Reference frame</strong> - The reference frame used to define the position of the rotation
35 * axes. This can either be <em>relative (intrinsic)</em> or <em>absolute (extrinsic)</em>. A relative
36 * reference frame defines the rotation axes from the point of view of the "thing" being rotated.
37 * Thus, each rotation after the first occurs around an axis that very well may have been
38 * moved from its original position by a previous rotation. A good example of this is an
39 * airplane: the pilot steps through a sequence of rotations, each time moving the airplane
40 * around its own up/down, left/right, and front/back axes, regardless of how the airplane
41 * is oriented at the time. In contrast, an absolute reference frame is fixed and does not
42 * move with each rotation.
43 * </li>
44 * <li><strong>Rotation direction</strong> - This defines the rotation direction that angles are measured in.
45 * This library uses <em>right-handed rotations</em> exclusively. This means that the direction of rotation
46 * around an axis is the same as the curl of one's fingers when the right hand is placed on the axis
47 * with the thumb pointing in the axis direction.
48 * </li>
49 * </ul>
50 *
51 * <p>
52 * Computations involving multiple rotations are generally very complicated when using axis-angle sequences. Therefore, it is recommended
53 * to only use this class to represent angles and orientations when needed in this form, and to use {@link QuaternionRotation}
54 * for everything else. Quaternions are much easier to work with and avoid many of the problems of axis-angle sequence representations,
55 * such as <a href="https://en.wikipedia.org/wiki/Gimbal_lock">gimbal lock</a>.
56 * </p>
57 *
58 * @see <a href="https://en.wikipedia.org/wiki/Euler_angles">Euler Angles</a>
59 * @see QuaternionRotation
60 */
61 public final class AxisAngleSequence {
62 /** Reference frame for defining axis positions. */
63 private final AxisReferenceFrame referenceFrame;
64
65 /** Axis sequence. */
66 private final AxisSequence axisSequence;
67
68 /** Angle around the first rotation axis, in radians. */
69 private final double angle1;
70
71 /** Angle around the second rotation axis, in radians. */
72 private final double angle2;
73
74 /** Angle around the third rotation axis, in radians. */
75 private final double angle3;
76
77 /** Construct an instance from its component parts.
78 * @param referenceFrame the axis reference frame
79 * @param axisSequence the axis rotation sequence
80 * @param angle1 angle around the first axis in radians
81 * @param angle2 angle around the second axis in radians
82 * @param angle3 angle around the third axis in radians
83 */
84 public AxisAngleSequence(final AxisReferenceFrame referenceFrame, final AxisSequence axisSequence,
85 final double angle1, final double angle2, final double angle3) {
86 this.referenceFrame = referenceFrame;
87 this.axisSequence = axisSequence;
88
89 this.angle1 = angle1;
90 this.angle2 = angle2;
91 this.angle3 = angle3;
92 }
93
94 /** Get the axis reference frame. This defines the position of the rotation axes.
95 * @return the axis reference frame
96 */
97 public AxisReferenceFrame getReferenceFrame() {
98 return referenceFrame;
99 }
100
101 /** Get the rotation axis sequence.
102 * @return the rotation axis sequence
103 */
104 public AxisSequence getAxisSequence() {
105 return axisSequence;
106 }
107
108 /** Get the angle of rotation around the first axis, in radians.
109 * @return angle of rotation around the first axis, in radians
110 */
111 public double getAngle1() {
112 return angle1;
113 }
114
115 /** Get the angle of rotation around the second axis, in radians.
116 * @return angle of rotation around the second axis, in radians
117 */
118 public double getAngle2() {
119 return angle2;
120 }
121
122 /** Get the angle of rotation around the third axis, in radians.
123 * @return angle of rotation around the third axis, in radians
124 */
125 public double getAngle3() {
126 return angle3;
127 }
128
129 /** Get the rotation angles as a 3-element array.
130 * @return an array containing the 3 rotation angles
131 */
132 public double[] getAngles() {
133 return new double[]{angle1, angle2, angle3};
134 }
135
136 /** {@inheritDoc} */
137 @Override
138 public int hashCode() {
139 return 107 * (199 * Objects.hash(referenceFrame, axisSequence)) +
140 (7 * Double.hashCode(angle1)) +
141 (11 * Double.hashCode(angle2)) +
142 (19 * Double.hashCode(angle3));
143 }
144
145 /** {@inheritDoc} */
146 @Override
147 public boolean equals(final Object obj) {
148 if (this == obj) {
149 return true;
150 }
151 if (!(obj instanceof AxisAngleSequence)) {
152 return false;
153 }
154
155 final AxisAngleSequence other = (AxisAngleSequence) obj;
156
157 return this.referenceFrame == other.referenceFrame &&
158 this.axisSequence == other.axisSequence &&
159 Double.compare(this.angle1, other.angle1) == 0 &&
160 Double.compare(this.angle2, other.angle2) == 0 &&
161 Double.compare(this.angle3, other.angle3) == 0;
162 }
163
164 /** {@inheritDoc} */
165 @Override
166 public String toString() {
167 final StringBuilder sb = new StringBuilder();
168 sb.append(this.getClass().getSimpleName())
169 .append("[referenceFrame=")
170 .append(referenceFrame)
171 .append(", axisSequence=")
172 .append(axisSequence)
173 .append(", angles=")
174 .append(Arrays.toString(getAngles()))
175 .append(']');
176
177 return sb.toString();
178 }
179
180 /** Create a new instance with a reference frame of {@link AxisReferenceFrame#RELATIVE}.
181 * @param axisSequence the axis rotation sequence
182 * @param angle1 angle around the first axis in radians
183 * @param angle2 angle around the second axis in radians
184 * @param angle3 angle around the third axis in radians
185 * @return a new instance with a relative reference frame
186 */
187 public static AxisAngleSequence createRelative(final AxisSequence axisSequence, final double angle1,
188 final double angle2, final double angle3) {
189 return new AxisAngleSequence(AxisReferenceFrame.RELATIVE, axisSequence, angle1, angle2, angle3);
190 }
191
192 /** Create a new instance with a reference frame of {@link AxisReferenceFrame#ABSOLUTE}.
193 * @param axisSequence the axis rotation sequence
194 * @param angle1 angle around the first axis in radians
195 * @param angle2 angle around the second axis in radians
196 * @param angle3 angle around the third axis in radians
197 * @return a new instance with an absolute reference frame
198 */
199 public static AxisAngleSequence createAbsolute(final AxisSequence axisSequence, final double angle1,
200 final double angle2, final double angle3) {
201 return new AxisAngleSequence(AxisReferenceFrame.ABSOLUTE, axisSequence, angle1, angle2, angle3);
202 }
203 }