RotationOrder.java

  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.  *      https://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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.geometry.euclidean.threed;

  22. import org.hipparchus.CalculusFieldElement;
  23. import org.hipparchus.exception.MathIllegalStateException;
  24. import org.hipparchus.geometry.LocalizedGeometryFormats;
  25. import org.hipparchus.util.FastMath;
  26. import org.hipparchus.util.MathArrays;
  27. import org.hipparchus.util.MathUtils;

  28. /** Enumerate representing a rotation order specification for Cardan or Euler angles.
  29.  * <p>
  30.  * Since Hipparchus 1.7 this class is an enumerate class.
  31.  * </p>
  32.  */
  33. public enum RotationOrder {

  34.     /** Set of Cardan angles.
  35.      * this ordered set of rotations is around X, then around Y, then
  36.      * around Z
  37.      */
  38.      XYZ(RotationStage.X, RotationStage.Y, RotationStage.Z),

  39.     /** Set of Cardan angles.
  40.      * this ordered set of rotations is around X, then around Z, then
  41.      * around Y
  42.      */
  43.      XZY(RotationStage.X, RotationStage.Z, RotationStage.Y),

  44.     /** Set of Cardan angles.
  45.      * this ordered set of rotations is around Y, then around X, then
  46.      * around Z
  47.      */
  48.      YXZ(RotationStage.Y, RotationStage.X, RotationStage.Z),

  49.     /** Set of Cardan angles.
  50.      * this ordered set of rotations is around Y, then around Z, then
  51.      * around X
  52.      */
  53.      YZX(RotationStage.Y, RotationStage.Z, RotationStage.X),

  54.     /** Set of Cardan angles.
  55.      * this ordered set of rotations is around Z, then around X, then
  56.      * around Y
  57.      */
  58.      ZXY(RotationStage.Z, RotationStage.X, RotationStage.Y),

  59.     /** Set of Cardan angles.
  60.      * this ordered set of rotations is around Z, then around Y, then
  61.      * around X
  62.      */
  63.      ZYX(RotationStage.Z, RotationStage.Y, RotationStage.X),

  64.     /** Set of Euler angles.
  65.      * this ordered set of rotations is around X, then around Y, then
  66.      * around X
  67.      */
  68.      XYX(RotationStage.X, RotationStage.Y, RotationStage.X),

  69.     /** Set of Euler angles.
  70.      * this ordered set of rotations is around X, then around Z, then
  71.      * around X
  72.      */
  73.      XZX(RotationStage.X, RotationStage.Z, RotationStage.X),

  74.     /** Set of Euler angles.
  75.      * this ordered set of rotations is around Y, then around X, then
  76.      * around Y
  77.      */
  78.      YXY(RotationStage.Y, RotationStage.X, RotationStage.Y),

  79.     /** Set of Euler angles.
  80.      * this ordered set of rotations is around Y, then around Z, then
  81.      * around Y
  82.      */
  83.      YZY(RotationStage.Y, RotationStage.Z, RotationStage.Y),

  84.     /** Set of Euler angles.
  85.      * this ordered set of rotations is around Z, then around X, then
  86.      * around Z
  87.      */
  88.      ZXZ(RotationStage.Z, RotationStage.X, RotationStage.Z),

  89.     /** Set of Euler angles.
  90.      * this ordered set of rotations is around Z, then around Y, then
  91.      * around Z
  92.      */
  93.      ZYZ(RotationStage.Z, RotationStage.Y, RotationStage.Z);

  94.     /** Switch to safe computation of asin/acos.
  95.      * @since 3.1
  96.      */
  97.     private static final double SAFE_SWITCH = 0.999;

  98.     /** Name of the rotations order. */
  99.     private final String name;

  100.     /** First stage. */
  101.     private final RotationStage stage1;

  102.     /** Second stage. */
  103.     private final RotationStage stage2;

  104.     /** Third stage. */
  105.     private final RotationStage stage3;

  106.     /** Missing stage (for Euler rotations). */
  107.     private final RotationStage missing;

  108.     /** Sign for direct order (i.e. X before Y, Y before Z, Z before X). */
  109.     private final double sign;

  110.     /** Enum constructor.
  111.      * @param stage1 first stage
  112.      * @param stage2 second stage
  113.      * @param stage3 third stage
  114.      */
  115.     RotationOrder(final RotationStage stage1, final RotationStage stage2, final RotationStage stage3) {
  116.         this.name   = stage1.name() + stage2.name() + stage3.name();
  117.         this.stage1 = stage1;
  118.         this.stage2 = stage2;
  119.         this.stage3 = stage3;

  120.         if (stage1 == stage3) {
  121.             // Euler rotations
  122.             if (stage1 != RotationStage.X && stage2 != RotationStage.X) {
  123.                 missing = RotationStage.X;
  124.             } else if (stage1 != RotationStage.Y && stage2 != RotationStage.Y) {
  125.                 missing = RotationStage.Y;
  126.             } else {
  127.                 missing = RotationStage.Z;
  128.             }
  129.         } else {
  130.             // Cardan rotations
  131.             missing = null;
  132.         }

  133.         // check if the first two rotations are in direct or indirect order
  134.         final Vector3D a1 = stage1.getAxis();
  135.         final Vector3D a2 = stage2.getAxis();
  136.         final Vector3D a3 = missing == null ? stage3.getAxis() : missing.getAxis();
  137.         sign = FastMath.copySign(1.0, Vector3D.dotProduct(a3, Vector3D.crossProduct(a1, a2)));

  138.     }

  139.     /** Get a string representation of the instance.
  140.      * @return a string representation of the instance (in fact, its name)
  141.      */
  142.     @Override
  143.     public String toString() {
  144.         return name;
  145.     }

  146.     /** Get the axis of the first rotation.
  147.      * @return axis of the first rotation
  148.      */
  149.     public Vector3D getA1() {
  150.         return stage1.getAxis();
  151.     }

  152.     /** Get the axis of the second rotation.
  153.      * @return axis of the second rotation
  154.      */
  155.     public Vector3D getA2() {
  156.         return stage2.getAxis();
  157.     }

  158.     /** Get the axis of the third rotation.
  159.      * @return axis of the third rotation
  160.      */
  161.     public Vector3D getA3() {
  162.         return stage3.getAxis();
  163.     }

  164.     /** Get the rotation order corresponding to a string representation.
  165.      * @param value name
  166.      * @return a rotation order object
  167.      * @since 1.7
  168.      */
  169.     public static RotationOrder getRotationOrder(final String value) {
  170.         try {
  171.             return RotationOrder.valueOf(value);
  172.         } catch (IllegalArgumentException iae) {
  173.             // Invalid value. An exception is thrown
  174.             throw new MathIllegalStateException(iae,
  175.                                                 LocalizedGeometryFormats.INVALID_ROTATION_ORDER_NAME,
  176.                                                 value);
  177.         }
  178.     }

  179.     /** Get the Cardan or Euler angles corresponding to the instance.
  180.      * <p>
  181.      * The algorithm used here works even when the rotation is exactly at the
  182.      * the singularity of the rotation order and convention. In this case, one of
  183.      * the angles in the singular pair is arbitrarily set to exactly 0 and the
  184.      * second angle is computed. The angle set to 0 in the singular case is the
  185.      * angle of the first rotation in the case of Cardan orders, and it is the angle
  186.      * of the last rotation in the case of Euler orders. This implies that extracting
  187.      * the angles of a rotation never fails (it used to trigger an exception in singular
  188.      * cases up to Hipparchus 3.0).
  189.      * </p>
  190.      * @param rotation rotation from which angles should be extracted
  191.      * @param convention convention to use for the semantics of the angle
  192.      * @return an array of three angles, in the order specified by the set
  193.      * @since 3.1
  194.      */
  195.     public double[] getAngles(final Rotation rotation, final RotationConvention convention) {

  196.         // Cardan/Euler angles rotations matrices have the following form, taking
  197.         // RotationOrder.XYZ (φ about X, θ about Y, ψ about Z) and RotationConvention.FRAME_TRANSFORM
  198.         // as an example:
  199.         // [  cos θ cos ψ     cos φ sin ψ + sin φ sin θ cos ψ     sin φ sin ψ - cos φ sin θ cos ψ ]
  200.         // [ -cos θ sin ψ     cos φ cos ψ - sin φ sin θ sin ψ     cos ψ sin φ + cos φ sin θ sin ψ ]
  201.         // [  sin θ                       - sin φ cos θ                         cos φ cos θ       ]

  202.         // One can see that there is a "simple" column (the first column in the example above) and a "simple"
  203.         // row (the last row in the example above). In fact, the simple column always corresponds to the axis
  204.         // of the first rotation in RotationConvention.FRAME_TRANSFORM convention (i.e. X axis here, hence
  205.         // first column) and to the axis of the third rotation in RotationConvention.VECTOR_OPERATOR
  206.         // convention. The "simple" row always corresponds to axis of the third rotation in
  207.         // RotationConvention.FRAME_TRANSFORM convention (i.e. Z axis here, hence last row) and to the axis
  208.         // of the first rotation in RotationConvention.VECTOR_OPERATOR convention.
  209.         final Vector3D vCol = getColumnVector(rotation, convention);
  210.         final Vector3D vRow = getRowVector(rotation, convention);

  211.         // in the example above, we see that the components of the simple row
  212.         // are [ sin θ, -sin φ cos θ, cos φ cos θ ], which means that if we select
  213.         // θ strictly between -π/2 and +π/2 (i.e. cos θ > 0), then we can extract
  214.         // φ as atan2(-Yrow, +Zrow), i.e. the coordinates along the axes of the
  215.         // two final rotations in the Cardan sequence.

  216.         // in the example above, we see that the components of the simple column
  217.         // are [ cos θ cos ψ, -cos θ sin ψ, sin θ ], which means that if we select
  218.         // θ strictly between -π/2 and +π/2 (i.e. cos θ > 0), then we can extract
  219.         // ψ as atan2(-Ycol, +Xcol), i.e. the coordinates along the axes of the
  220.         // two initial rotations in the Cardan sequence.

  221.         // if we are exactly on the singularity (i.e. θ = ±π/2), then the matrix
  222.         // degenerates to:
  223.         // [  0    sin(ψ ± φ)    ∓ cos(ψ ± φ) ]
  224.         // [  0    cos(ψ ± φ)    ± sin(ψ ± φ) ]
  225.         // [ ±1       0              0        ]
  226.         // at singularity, there is an infinite number of pairs (here ψ and φ) that represent
  227.         // the same rotation, so we have to make some assumptions, arbitrarily setting one angle
  228.         // of the pair and computing the other one from the non-singular middle column in the
  229.         // FRAME_TRANSFORM rotation convention or from the non-singular middle row in the
  230.         // VECTOR_OPERATOR rotation convention. In our XYZ rotation order and FRAME_TRANSFORM
  231.         // rotation convention example, we can extract a consistent set of angles set by:
  232.         //   - setting θ = ±π/2 copying the sign from ±1 term,
  233.         //   - arbitrarily set φ = 0
  234.         //   - compute ψ = atan2(XmidCol, YmidCol)

  235.         // These results generalize to all sequences, with some adjustments for
  236.         // Euler sequence where we need to replace one axis by the missing axis
  237.         // and some sign adjustments depending on the rotation order being direct
  238.         // (i.e. X before Y, Y before Z, Z before X) or indirect

  239.         if (missing == null) {
  240.             // this is a Cardan angles order

  241.             if (convention == RotationConvention.FRAME_TRANSFORM) {
  242.                 final double s = stage2.getComponent(vRow) * -sign;
  243.                 final double c = stage3.getComponent(vRow);
  244.                 if (s * s + c * c > 1.0e-30) {
  245.                     // regular case
  246.                     return new double[] {
  247.                         FastMath.atan2(s, c),
  248.                         safeAsin(stage1.getComponent(vRow), stage2.getComponent(vRow), stage3.getComponent(vRow)) * sign,
  249.                         FastMath.atan2(stage2.getComponent(vCol) * -sign, stage1.getComponent(vCol))
  250.                     };
  251.                 } else {
  252.                     // near singularity
  253.                     final Vector3D midCol = rotation.applyTo(stage2.getAxis());
  254.                     return new double[] {
  255.                         0.0,
  256.                         FastMath.copySign(MathUtils.SEMI_PI, stage1.getComponent(vRow) * sign),
  257.                         FastMath.atan2(stage1.getComponent(midCol) * sign, stage2.getComponent(midCol))
  258.                     };
  259.                 }
  260.             } else {
  261.                 final double s = stage2.getComponent(vCol) * -sign;
  262.                 final double c = stage3.getComponent(vCol);
  263.                 if (s * s + c * c > 1.0e-30) {
  264.                     // regular case
  265.                     return new double[] {
  266.                         FastMath.atan2(s, c),
  267.                         safeAsin(stage3.getComponent(vRow), stage1.getComponent(vRow), stage2.getComponent(vRow)) * sign,
  268.                         FastMath.atan2(stage2.getComponent(vRow) * -sign, stage1.getComponent(vRow))
  269.                     };
  270.                 } else {
  271.                     // near singularity
  272.                     final Vector3D midRow = rotation.applyInverseTo(stage2.getAxis());
  273.                     return new double[] {
  274.                         0.0,
  275.                         FastMath.copySign(MathUtils.SEMI_PI, stage3.getComponent(vRow) * sign),
  276.                         FastMath.atan2(stage1.getComponent(midRow) * sign, stage2.getComponent(midRow))
  277.                     };
  278.                 }
  279.             }
  280.         } else {
  281.             // this is an Euler angles order
  282.             if (convention == RotationConvention.FRAME_TRANSFORM) {
  283.                 final double s = stage2.getComponent(vRow);
  284.                 final double c = missing.getComponent(vRow) * -sign;
  285.                 if (s * s + c * c > 1.0e-30) {
  286.                     // regular case
  287.                     return new double[] {
  288.                         FastMath.atan2(s, c),
  289.                         safeAcos(stage1.getComponent(vRow), stage2.getComponent(vRow), missing.getComponent(vRow)),
  290.                         FastMath.atan2(stage2.getComponent(vCol), missing.getComponent(vCol) * sign)
  291.                     };
  292.                 } else {
  293.                     // near singularity
  294.                     final Vector3D midCol = rotation.applyTo(stage2.getAxis());
  295.                     return new double[] {
  296.                         FastMath.atan2(missing.getComponent(midCol) * -sign, stage2.getComponent(midCol)) *
  297.                         FastMath.copySign(1, stage1.getComponent(vRow)),
  298.                         stage1.getComponent(vRow) > 0 ? 0 : FastMath.PI,
  299.                         0.0
  300.                     };
  301.                 }
  302.             } else {
  303.                 final double s = stage2.getComponent(vCol);
  304.                 final double c = missing.getComponent(vCol) * -sign;
  305.                 if (s * s + c * c > 1.0e-30) {
  306.                     // regular case
  307.                     return new double[] {
  308.                         FastMath.atan2(s, c),
  309.                         safeAcos(stage1.getComponent(vRow), stage2.getComponent(vRow), missing.getComponent(vRow)),
  310.                         FastMath.atan2(stage2.getComponent(vRow), missing.getComponent(vRow) * sign)
  311.                     };
  312.                 } else {
  313.                     // near singularity
  314.                     final Vector3D midRow = rotation.applyInverseTo(stage2.getAxis());
  315.                     return new double[] {
  316.                         FastMath.atan2(missing.getComponent(midRow) * -sign, stage2.getComponent(midRow)) *
  317.                         FastMath.copySign(1, stage1.getComponent(vRow)),
  318.                         stage1.getComponent(vRow) > 0 ? 0 : FastMath.PI,
  319.                         0.0
  320.                     };
  321.                 }
  322.             }
  323.         }
  324.     }

  325.     /** Get the Cardan or Euler angles corresponding to the instance.
  326.      * <p>
  327.      * The algorithm used here works even when the rotation is exactly at the
  328.      * the singularity of the rotation order and convention. In this case, one of
  329.      * the angles in the singular pair is arbitrarily set to exactly 0 and the
  330.      * second angle is computed. The angle set to 0 in the singular case is the
  331.      * angle of the first rotation in the case of Cardan orders, and it is the angle
  332.      * of the last rotation in the case of Euler orders. This implies that extracting
  333.      * the angles of a rotation never fails (it used to trigger an exception in singular
  334.      * cases up to Hipparchus 3.0).
  335.      * </p>
  336.      * @param <T> type of the field elements
  337.      * @param rotation rotation from which angles should be extracted
  338.      * @param convention convention to use for the semantics of the angle
  339.      * @return an array of three angles, in the order specified by the set
  340.      * @since 3.1
  341.      */
  342.     public <T extends CalculusFieldElement<T>> T[] getAngles(final FieldRotation<T> rotation, final RotationConvention convention) {

  343.         // Cardan/Euler angles rotations matrices have the following form, taking
  344.         // RotationOrder.XYZ (φ about X, θ about Y, ψ about Z) and RotationConvention.FRAME_TRANSFORM
  345.         // as an example:
  346.         // [  cos θ cos ψ     cos φ sin ψ + sin φ sin θ cos ψ     sin φ sin ψ - cos φ sin θ cos ψ ]
  347.         // [ -cos θ sin ψ     cos φ cos ψ - sin φ sin θ sin ψ     cos ψ sin φ + cos φ sin θ sin ψ ]
  348.         // [  sin θ                       - sin φ cos θ                         cos φ cos θ       ]

  349.         // One can see that there is a "simple" column (the first column in the example above) and a "simple"
  350.         // row (the last row in the example above). In fact, the simple column always corresponds to the axis
  351.         // of the first rotation in RotationConvention.FRAME_TRANSFORM convention (i.e. X axis here, hence
  352.         // first column) and to the axis of the third rotation in RotationConvention.VECTOR_OPERATOR
  353.         // convention. The "simple" row always corresponds to axis of the third rotation in
  354.         // RotationConvention.FRAME_TRANSFORM convention (i.e. Z axis here, hence last row) and to the axis
  355.         // of the first rotation in RotationConvention.VECTOR_OPERATOR convention.
  356.         final FieldVector3D<T> vCol = getColumnVector(rotation, convention);
  357.         final FieldVector3D<T> vRow = getRowVector(rotation, convention);

  358.         // in the example above, we see that the components of the simple row
  359.         // are [ sin θ, -sin φ cos θ, cos φ cos θ ], which means that if we select
  360.         // θ strictly between -π/2 and +π/2 (i.e. cos θ > 0), then we can extract
  361.         // φ as atan2(-Yrow, +Zrow), i.e. the coordinates along the axes of the
  362.         // two final rotations in the Cardan sequence.

  363.         // in the example above, we see that the components of the simple column
  364.         // are [ cos θ cos ψ, -cos θ sin ψ, sin θ ], which means that if we select
  365.         // θ strictly between -π/2 and +π/2 (i.e. cos θ > 0), then we can extract
  366.         // ψ as atan2(-Ycol, +Xcol), i.e. the coordinates along the axes of the
  367.         // two initial rotations in the Cardan sequence.

  368.         // if we are exactly on the singularity (i.e. θ = ±π/2), then the matrix
  369.         // degenerates to:
  370.         // [  0    sin(ψ ± φ)    ∓ cos(ψ ± φ) ]
  371.         // [  0    cos(ψ ± φ)    ± sin(ψ ± φ) ]
  372.         // [ ±1       0              0        ]
  373.         // at singularity, there is an infinite number of pairs (here ψ and φ) that represent
  374.         // the same rotation, so we have to make some assumptions, arbitrarily setting one angle
  375.         // of the pair and computing the other one from the non-singular middle column in the
  376.         // FRAME_TRANSFORM rotation convention or from the non-singular middle row in the
  377.         // VECTOR_OPERATOR rotation convention. In our XYZ rotation order and FRAME_TRANSFORM
  378.         // rotation convention example, we can extract a consistent set of angles set by:
  379.         //   - setting θ = ±π/2 copying the sign from ±1 term,
  380.         //   - arbitrarily set φ = 0
  381.         //   - compute ψ = atan2(XmidCol, YmidCol)

  382.         // These results generalize to all sequences, with some adjustments for
  383.         // Euler sequence where we need to replace one axis by the missing axis
  384.         // and some sign adjustments depending on the rotation order being direct
  385.         // (i.e. X before Y, Y before Z, Z before X) or indirect

  386.         if (missing == null) {
  387.             // this is a Cardan angles order
  388.             if (convention == RotationConvention.FRAME_TRANSFORM) {
  389.                 final T s = stage2.getComponent(vRow).multiply(-sign);
  390.                 final T c = stage3.getComponent(vRow);
  391.                 if (s.square().add(c.square()).getReal() > 1.0e-30) {
  392.                     // regular case
  393.                     return buildArray(FastMath.atan2(s, c),
  394.                                       safeAsin(stage1.getComponent(vRow), stage2.getComponent(vRow), stage3.getComponent(vRow)).multiply(sign),
  395.                                       FastMath.atan2(stage2.getComponent(vCol).multiply(-sign), stage1.getComponent(vCol)));
  396.                 } else {
  397.                     // near singularity
  398.                     final FieldVector3D<T> midCol = rotation.applyTo(stage2.getAxis());
  399.                     return buildArray(s.getField().getZero(),
  400.                                       FastMath.copySign(s.getPi().multiply(0.5), stage1.getComponent(vRow).multiply(sign)),
  401.                                       FastMath.atan2(stage1.getComponent(midCol).multiply(sign), stage2.getComponent(midCol)));
  402.                 }
  403.             } else {
  404.                 final T s = stage2.getComponent(vCol).multiply(-sign);
  405.                 final T c = stage3.getComponent(vCol);
  406.                 if (s.square().add(c.square()).getReal() > 1.0e-30) {
  407.                     // regular case
  408.                     return buildArray(FastMath.atan2(stage2.getComponent(vCol).multiply(-sign), stage3.getComponent(vCol)),
  409.                                       safeAsin(stage3.getComponent(vRow), stage1.getComponent(vRow), stage2.getComponent(vRow)).multiply(sign),
  410.                                       FastMath.atan2(stage2.getComponent(vRow).multiply(-sign), stage1.getComponent(vRow)));
  411.                 } else {
  412.                     // near singularity
  413.                     final FieldVector3D<T> midRow = rotation.applyInverseTo(stage2.getAxis());
  414.                     return buildArray(s.getField().getZero(),
  415.                                       FastMath.copySign(s.getPi().multiply(0.5), stage3.getComponent(vRow).multiply(sign)),
  416.                                       FastMath.atan2(stage1.getComponent(midRow).multiply(sign), stage2.getComponent(midRow)));
  417.                 }
  418.             }
  419.         } else {
  420.             // this is an Euler angles order
  421.             if (convention == RotationConvention.FRAME_TRANSFORM) {
  422.                 final T s = stage2.getComponent(vRow);
  423.                 final T c = missing.getComponent(vRow).multiply(-sign);
  424.                 if (s.square().add(c.square()).getReal() > 1.0e-30) {
  425.                     // regular case
  426.                     return buildArray(FastMath.atan2(s, c),
  427.                                       safeAcos(stage1.getComponent(vRow), stage2.getComponent(vRow), missing.getComponent(vRow)),
  428.                                       FastMath.atan2(stage2.getComponent(vCol), missing.getComponent(vCol).multiply(sign)));
  429.                 } else {
  430.                     // near singularity
  431.                     final FieldVector3D<T> midCol = rotation.applyTo(stage2.getAxis());
  432.                     return buildArray(FastMath.atan2(missing.getComponent(midCol).multiply(-sign), stage2.getComponent(midCol)).
  433.                                       multiply(FastMath.copySign(1, stage1.getComponent(vRow).getReal())),
  434.                                       stage1.getComponent(vRow).getReal() > 0 ? s.getField().getZero() : s.getPi(),
  435.                                       s.getField().getZero());
  436.                 }
  437.             } else {
  438.                 final T s = stage2.getComponent(vCol);
  439.                 final T c = missing.getComponent(vCol).multiply(-sign);
  440.                 if (s.square().add(c.square()).getReal() > 1.0e-30) {
  441.                     // regular case
  442.                     return buildArray(FastMath.atan2(s, c),
  443.                                       safeAcos(stage1.getComponent(vRow), stage2.getComponent(vRow), missing.getComponent(vRow)),
  444.                                       FastMath.atan2(stage2.getComponent(vRow), missing.getComponent(vRow).multiply(sign)));
  445.                 } else {
  446.                     // near singularity
  447.                     final FieldVector3D<T> midRow = rotation.applyInverseTo(stage2.getAxis());
  448.                     return buildArray(FastMath.atan2(missing.getComponent(midRow).multiply(-sign), stage2.getComponent(midRow)).
  449.                                       multiply(FastMath.copySign(1, stage1.getComponent(vRow).getReal())),
  450.                                       stage1.getComponent(vRow).getReal() > 0 ? s.getField().getZero() : s.getPi(),
  451.                                       s.getField().getZero());
  452.                 }
  453.             }
  454.         }
  455.     }

  456.     /** Get the simplest column vector from the rotation matrix.
  457.      * @param rotation rotation
  458.      * @param convention convention to use for the semantics of the angle
  459.      * @return column vector
  460.      * @since 3.1
  461.      */
  462.     private Vector3D getColumnVector(final Rotation rotation,
  463.                                      final RotationConvention convention) {
  464.         return rotation.applyTo(convention == RotationConvention.VECTOR_OPERATOR ?
  465.                                 stage3.getAxis() :
  466.                                 stage1.getAxis());
  467.     }

  468.     /** Get the simplest row vector from the rotation matrix.
  469.      * @param rotation rotation
  470.      * @param convention convention to use for the semantics of the angle
  471.      * @return row vector
  472.      * @since 3.1
  473.      */
  474.     private Vector3D getRowVector(final Rotation rotation,
  475.                                   final RotationConvention convention) {
  476.         return rotation.applyInverseTo(convention == RotationConvention.VECTOR_OPERATOR ?
  477.                                        stage1.getAxis() :
  478.                                        stage3.getAxis());
  479.     }

  480.     /** Get the simplest column vector from the rotation matrix.
  481.      * @param <T> type of the field elements
  482.      * @param rotation rotation
  483.      * @param convention convention to use for the semantics of the angle
  484.      * @return column vector
  485.      * @since 3.1
  486.      */
  487.     private <T extends CalculusFieldElement<T>> FieldVector3D<T> getColumnVector(final FieldRotation<T> rotation,
  488.                                                                                  final RotationConvention convention) {
  489.         return rotation.applyTo(convention == RotationConvention.VECTOR_OPERATOR ?
  490.                                 stage3.getAxis() :
  491.                                 stage1.getAxis());
  492.     }

  493.     /** Get the simplest row vector from the rotation matrix.
  494.      * @param <T> type of the field elements
  495.      * @param rotation rotation
  496.      * @param convention convention to use for the semantics of the angle
  497.      * @return row vector
  498.      * @since 3.1
  499.      */
  500.     private <T extends CalculusFieldElement<T>> FieldVector3D<T> getRowVector(final FieldRotation<T> rotation,
  501.                                                                               final RotationConvention convention) {
  502.         return rotation.applyInverseTo(convention == RotationConvention.VECTOR_OPERATOR ?
  503.                                        stage1.getAxis() :
  504.                                        stage3.getAxis());
  505.     }

  506.     /** Safe computation of acos(some vector coordinate) working around singularities.
  507.      * @param cos  cosine coordinate
  508.      * @param sin1 one of the sine coordinates
  509.      * @param sin2 other sine coordinate
  510.      * @return acos of the coordinate
  511.      * @since 3.1
  512.      */
  513.     private static double safeAcos(final double cos, final double sin1, final double sin2) {
  514.         if (cos < -SAFE_SWITCH) {
  515.             return FastMath.PI - FastMath.asin(FastMath.sqrt(sin1 * sin1 + sin2 * sin2));
  516.         } else if (cos > SAFE_SWITCH) {
  517.             return FastMath.asin(FastMath.sqrt(sin1 * sin1 + sin2 * sin2));
  518.         } else {
  519.             return FastMath.acos(cos);
  520.         }
  521.     }

  522.     /** Safe computation of asin(some vector coordinate) working around singularities.
  523.      * @param sin sine coordinate
  524.      * @param cos1 one of the cosine coordinates
  525.      * @param cos2 other cosine coordinate
  526.      * @return asin of the coordinate
  527.      * @since 3.1
  528.      */
  529.     private static double safeAsin(final double sin, final double cos1, final double cos2) {
  530.         if (sin < -SAFE_SWITCH) {
  531.             return -FastMath.acos(FastMath.sqrt(cos1 * cos1 + cos2 * cos2));
  532.         } else if (sin > SAFE_SWITCH) {
  533.             return FastMath.acos(FastMath.sqrt(cos1 * cos1 + cos2 * cos2));
  534.         } else {
  535.             return FastMath.asin(sin);
  536.         }
  537.     }

  538.     /** Safe computation of acos(some vector coordinate) working around singularities.
  539.      * @param <T> type of the field elements
  540.      * @param cos  cosine coordinate
  541.      * @param sin1 one of the sine coordinates
  542.      * @param sin2 other sine coordinate
  543.      * @return acos of the coordinate
  544.      * @since 3.1
  545.      */
  546.     private static <T extends CalculusFieldElement<T>> T safeAcos(final T cos,
  547.                                                                   final T sin1,
  548.                                                                   final T sin2) {
  549.         if (cos.getReal() < -SAFE_SWITCH) {
  550.             return FastMath.asin(FastMath.sqrt(sin1.square().add(sin2.square()))).subtract(sin1.getPi()).negate();
  551.         } else if (cos.getReal() > SAFE_SWITCH) {
  552.             return FastMath.asin(FastMath.sqrt(sin1.square().add(sin2.square())));
  553.         } else {
  554.             return FastMath.acos(cos);
  555.         }
  556.     }

  557.     /** Safe computation of asin(some vector coordinate) working around singularities.
  558.      * @param <T> type of the field elements
  559.      * @param sin sine coordinate
  560.      * @param cos1 one of the cosine coordinates
  561.      * @param cos2 other cosine coordinate
  562.      * @return asin of the coordinate
  563.      * @since 3.1
  564.      */
  565.     private static <T extends CalculusFieldElement<T>> T safeAsin(final T sin,
  566.                                                                   final T cos1,
  567.                                                                   final T cos2) {
  568.         if (sin.getReal() < -SAFE_SWITCH) {
  569.             return FastMath.acos(FastMath.sqrt(cos1.square().add(cos2.square()))).negate();
  570.         } else if (sin.getReal() > SAFE_SWITCH) {
  571.             return FastMath.acos(FastMath.sqrt(cos1.square().add(cos2.square())));
  572.         } else {
  573.             return FastMath.asin(sin);
  574.         }
  575.     }

  576.     /** Create a dimension 3 array.
  577.      * @param <T> type of the field elements
  578.      * @param a0 first array element
  579.      * @param a1 second array element
  580.      * @param a2 third array element
  581.      * @return new array
  582.      * @since 3.1
  583.      */
  584.     private static <T extends CalculusFieldElement<T>> T[] buildArray(final T a0, final T a1, final T a2) {
  585.         final T[] array = MathArrays.buildArray(a0.getField(), 3);
  586.         array[0] = a0;
  587.         array[1] = a1;
  588.         array[2] = a2;
  589.         return array;
  590.     }

  591. }