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 */
017package org.apache.commons.geometry.euclidean.threed.rotation;
018
019import java.util.Arrays;
020import java.util.Objects;
021
022/** <p>
023 * Class representing a sequence of axis-angle rotations. These types of
024 * rotations are commonly called <em>Euler angles</em>, <em>Tait-Bryan angles</em>,
025 * or <em>Cardan angles</em> depending on the properties of the rotation sequence and
026 * the particular use case. A sequence of three rotations around at least two different
027 * axes is sufficient to represent any rotation or orientation in 3 dimensional space.
028 * However, in order to unambiguously represent the rotation, the following information
029 * must be provided along with the rotation angles:
030 * <ul>
031 *      <li><strong>Axis sequence</strong> - The axes that the rotation angles are associated with and
032 *      in what order they occur.
033 *      </li>
034 *      <li><strong>Reference frame</strong> - The reference frame used to define the position of the rotation
035 *      axes. This can either be <em>relative (intrinsic)</em> or <em>absolute (extrinsic)</em>. A relative
036 *      reference frame defines the rotation axes from the point of view of the "thing" being rotated.
037 *      Thus, each rotation after the first occurs around an axis that very well may have been
038 *      moved from its original position by a previous rotation. A good example of this is an
039 *      airplane: the pilot steps through a sequence of rotations, each time moving the airplane
040 *      around its own up/down, left/right, and front/back axes, regardless of how the airplane
041 *      is oriented at the time. In contrast, an absolute reference frame is fixed and does not
042 *      move with each rotation.
043 *      </li>
044 *      <li><strong>Rotation direction</strong> - This defines the rotation direction that angles are measured in.
045 *      This library uses <em>right-handed rotations</em> exclusively. This means that the direction of rotation
046 *      around an axis is the same as the curl of one's fingers when the right hand is placed on the axis
047 *      with the thumb pointing in the axis direction.
048 *      </li>
049 * </ul>
050 *
051 * <p>
052 * Computations involving multiple rotations are generally very complicated when using axis-angle sequences. Therefore, it is recommended
053 * to only use this class to represent angles and orientations when needed in this form, and to use {@link QuaternionRotation}
054 * for everything else. Quaternions are much easier to work with and avoid many of the problems of axis-angle sequence representations,
055 * such as <a href="https://en.wikipedia.org/wiki/Gimbal_lock">gimbal lock</a>.
056 * </p>
057 *
058 * @see <a href="https://en.wikipedia.org/wiki/Euler_angles">Euler Angles</a>
059 * @see QuaternionRotation
060 */
061public final class AxisAngleSequence {
062    /** Reference frame for defining axis positions. */
063    private final AxisReferenceFrame referenceFrame;
064
065    /** Axis sequence. */
066    private final AxisSequence axisSequence;
067
068    /** Angle around the first rotation axis, in radians. */
069    private final double angle1;
070
071    /** Angle around the second rotation axis, in radians. */
072    private final double angle2;
073
074    /** Angle around the third rotation axis, in radians. */
075    private final double angle3;
076
077    /** Construct an instance from its component parts.
078     * @param referenceFrame the axis reference frame
079     * @param axisSequence the axis rotation sequence
080     * @param angle1 angle around the first axis in radians
081     * @param angle2 angle around the second axis in radians
082     * @param angle3 angle around the third axis in radians
083     */
084    public AxisAngleSequence(final AxisReferenceFrame referenceFrame, final AxisSequence axisSequence,
085            final double angle1, final double angle2, final double angle3) {
086        this.referenceFrame = referenceFrame;
087        this.axisSequence = axisSequence;
088
089        this.angle1 = angle1;
090        this.angle2 = angle2;
091        this.angle3 = angle3;
092    }
093
094    /** Get the axis reference frame. This defines the position of the rotation axes.
095     * @return the axis reference frame
096     */
097    public AxisReferenceFrame getReferenceFrame() {
098        return referenceFrame;
099    }
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}