/**
   * Returns a Quaternion created from three Euler angle rotations. The angles represent rotation
   * about their respective unit-axes. The angles are applied in the order X, Y, Z. Angles can be
   * extracted by calling {@link #getRotationX}, {@link #getRotationY}, {@link #getRotationZ}.
   *
   * @param x Angle rotation about unit-X axis.
   * @param y Angle rotation about unit-Y axis.
   * @param z Angle rotation about unit-Z axis.
   * @return Quaternion representation of the combined X-Y-Z rotation.
   */
  public static Quaternion fromRotationXYZ(Angle x, Angle y, Angle z) {
    if (x == null || y == null || z == null) {
      throw new IllegalArgumentException("Angle Is Null");
    }

    double cx = x.cosHalfAngle();
    double cy = y.cosHalfAngle();
    double cz = z.cosHalfAngle();
    double sx = x.sinHalfAngle();
    double sy = y.sinHalfAngle();
    double sz = z.sinHalfAngle();

    // The order in which the three Euler angles are applied is critical. This can be thought of as
    // multiplying
    // three quaternions together, one for each Euler angle (and corresponding unit axis). Like
    // matrices,
    // quaternions affect vectors in reverse order. For example, suppose we construct a quaternion
    //     Q = (QX * QX) * QZ
    // then transform some vector V by Q. This can be thought of as first transforming V by QZ, then
    // QY, and
    // finally by QX. This means that the order of quaternion multiplication is the reverse of the
    // order in which
    // the Euler angles are applied.
    //
    // The ordering below refers to the order in which angles are applied.
    //
    // QX = (sx, 0,  0,  cx)
    // QY = (0,  sy, 0,  cy)
    // QZ = (0,  0,  sz, cz)
    //
    // 1. XYZ Ordering
    // (QZ * QY * QX)
    // qw = (cx * cy * cz) + (sx * sy * sz);
    // qx = (sx * cy * cz) - (cx * sy * sz);
    // qy = (cx * sy * cz) + (sx * cy * sz);
    // qz = (cx * cy * sz) - (sx * sy * cz);
    //
    // 2. ZYX Ordering
    // (QX * QY * QZ)
    // qw = (cx * cy * cz) - (sx * sy * sz);
    // qx = (sx * cy * cz) + (cx * sy * sz);
    // qy = (cx * sy * cz) - (sx * cy * sz);
    // qz = (cx * cy * sz) + (sx * sy * cz);
    //

    double qw = (cx * cy * cz) + (sx * sy * sz);
    double qx = (sx * cy * cz) - (cx * sy * sz);
    double qy = (cx * sy * cz) + (sx * cy * sz);
    double qz = (cx * cy * sz) - (sx * sy * cz);

    return new Quaternion(qx, qy, qz, qw);
  }
  /**
   * Returns a Quaternion created from latitude and longitude rotations. Latitude and longitude can
   * be extracted from a Quaternion by calling {@link #getLatLon}.
   *
   * @param latitude Angle rotation of latitude.
   * @param longitude Angle rotation of longitude.
   * @return Quaternion representing combined latitude and longitude rotation.
   */
  public static Quaternion fromLatLon(Angle latitude, Angle longitude) {
    if (latitude == null || longitude == null) {
      throw new IllegalArgumentException("Angle Is Null");
    }

    double clat = latitude.cosHalfAngle();
    double clon = longitude.cosHalfAngle();
    double slat = latitude.sinHalfAngle();
    double slon = longitude.sinHalfAngle();

    // The order in which the lat/lon angles are applied is critical. This can be thought of as
    // multiplying two
    // quaternions together, one for each lat/lon angle. Like matrices, quaternions affect vectors
    // in reverse
    // order. For example, suppose we construct a quaternion
    //     Q = QLat * QLon
    // then transform some vector V by Q. This can be thought of as first transforming V by QLat,
    // then QLon. This
    // means that the order of quaternion multiplication is the reverse of the order in which the
    // lat/lon angles
    // are applied.
    //
    // The ordering below refers to order in which angles are applied.
    //
    // QLat = (0,    slat, 0, clat)
    // QLon = (slon, 0,    0, clon)
    //
    // 1. LatLon Ordering
    // (QLon * QLat)
    // qw = clat * clon;
    // qx = clat * slon;
    // qy = slat * clon;
    // qz = slat * slon;
    //
    // 2. LonLat Ordering
    // (QLat * QLon)
    // qw = clat * clon;
    // qx = clat * slon;
    // qy = slat * clon;
    // qz = - slat * slon;
    //

    double qw = clat * clon;
    double qx = clat * slon;
    double qy = slat * clon;
    double qz = 0.0 - slat * slon;

    return new Quaternion(qx, qy, qz, qw);
  }
  private static Quaternion fromAxisAngle(
      Angle angle, double axisX, double axisY, double axisZ, boolean normalize) {
    if (angle == null) {
      throw new IllegalArgumentException("Angle Is Null");
    }

    if (normalize) {
      double length = Math.sqrt((axisX * axisX) + (axisY * axisY) + (axisZ * axisZ));
      if (!isZero(length) && (length != 1.0)) {
        axisX /= length;
        axisY /= length;
        axisZ /= length;
      }
    }

    double s = angle.sinHalfAngle();
    double c = angle.cosHalfAngle();
    return new Quaternion(axisX * s, axisY * s, axisZ * s, c);
  }