I implemented the algorithm described in this article and it works very well:
http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/quat_2_euler_paper_ver2-1.pdf
The problem with the Wikipedia article mentioned in answer No. 1 is that it provides only the formula for XYZ rotation. The work given here provides a general algorithm that will work for any of the 12 sequences. You may need to read it several times and definitely work on an example. This is not the easiest to follow, but I have a block testing snot and it is pretty bulletproof.
In the first comment, here are the main components of my code. It should be enough for you to go:
The first class is "AxisType". The main feature that I use is 'getNextCircular ()' on it. It also makes it easy to convert code to Vectors.
public enum AxisType {
X("X"),
Y("Y"),
Z("Z");
String label;
AxisType(final String label) {
this.label = label;
}
public Vector3D toVector3D() {
if (equals(AxisType.X)) {
return new Vector3D(1,0,0);
} else if (equals(AxisType.Y)) {
return new Vector3D(0,1,0);
} else {
return new Vector3D(0,0,1);
}
}
public AxisType nextCircular() {
if (equals(AxisType.X)) {
return AxisType.Y;
} else if (equals(AxisType.Y)) {
return AxisType.Z;
} else {
return AxisType.X;
}
}
@Override
public String toString() {
return label;
}
}
EulerOrder, ( XYX, ZYX ..) . , ...
public class EulerOrder
{
private final AxisType[] axisOrder;
public EulerOrder(
final AxisType first,
final AxisType second,
final AxisType third )
{
axisOrder = new AxisType[] {
first,
second,
third
};
}
public Vector3D[] orderedAxis()
{
return new Vector3D[] {
axisOrder[0].toVector3D(),
axisOrder[1].toVector3D(),
axisOrder[2].toVector3D()
};
}
public AxisType getAxisType(
final int index )
{
if ((index > 2) || (index < 0))
{
throw new ArrayIndexOutOfBoundsException(
"EulerOrder[index] called with an invalid index");
}
return axisOrder[index];
}
public boolean isCircular()
{
return axisOrder[0].nextCircular().equals(
axisOrder[1]);
}
public boolean isRepeating()
{
return axisOrder[0] == axisOrder[2];
}
@Override
public String toString()
{
final StringBuffer buffer = new StringBuffer();
buffer.append(axisOrder[0].toString());
buffer.append(axisOrder[1].toString());
buffer.append(axisOrder[2].toString());
return buffer.toString();
}
public static EulerOrder XYZ()
{
return new EulerOrder(
AxisType.X,
AxisType.Y,
AxisType.Z);
}
public static EulerOrder YZX()
{
return new EulerOrder(
AxisType.Y,
AxisType.Z,
AxisType.X);
}
public static EulerOrder ZXY()
{
return new EulerOrder(
AxisType.Z,
AxisType.X,
AxisType.Y);
}
public static EulerOrder ZYX()
{
return new EulerOrder(
AxisType.Z,
AxisType.Y,
AxisType.X);
}
public static EulerOrder YXZ()
{
return new EulerOrder(
AxisType.Y,
AxisType.X,
AxisType.Z);
}
public static EulerOrder XZY()
{
return new EulerOrder(
AxisType.X,
AxisType.Z,
AxisType.Y);
}
public static EulerOrder XYX()
{
return new EulerOrder(
AxisType.X,
AxisType.Y,
AxisType.X);
}
public static EulerOrder XZX()
{
return new EulerOrder(
AxisType.X,
AxisType.Z,
AxisType.X);
}
public static EulerOrder YZY()
{
return new EulerOrder(
AxisType.Y,
AxisType.Z,
AxisType.Y);
}
public static EulerOrder YXY()
{
return new EulerOrder(
AxisType.Y,
AxisType.X,
AxisType.Y);
}
public static EulerOrder ZXZ()
{
return new EulerOrder(
AxisType.Z,
AxisType.X,
AxisType.Z);
}
public static EulerOrder ZYZ()
{
return new EulerOrder(
AxisType.Z,
AxisType.Y,
AxisType.Z);
}
public static EulerOrder parse(String eulerOrder)
{
if(eulerOrder.equals("XYZ")) return XYZ();
if(eulerOrder.equals("XZY")) return XZY();
if(eulerOrder.equals("YZX")) return YZX();
if(eulerOrder.equals("YXZ")) return YXZ();
if(eulerOrder.equals("ZYX")) return ZYX();
if(eulerOrder.equals("ZXY")) return ZXY();
if(eulerOrder.equals("XYX")) return XYX();
if(eulerOrder.equals("XZX")) return XZX();
if(eulerOrder.equals("YZY")) return YZY();
if(eulerOrder.equals("YXY")) return YXY();
if(eulerOrder.equals("ZYZ")) return ZYZ();
if(eulerOrder.equals("ZXZ")) return ZXZ();
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EulerOrder that = (EulerOrder) o;
if (!Arrays.equals(axisOrder, that.axisOrder)) return false;
return true;
}
@Override
public int hashCode() {
return axisOrder != null ? Arrays.hashCode(axisOrder) : 0;
}
}
. - .
public class EulerAngleDecomposer
{
private static EulerAngleDecomposer instance = null;
private EulerAngleDecomposer()
{
}
public static EulerAngleDecomposer getInstance()
{
if(instance == null)
instance = new EulerAngleDecomposer();
return instance;
}
private class IndexData
{
private final AxisType m_i1;
private final AxisType m_i1n;
private final AxisType m_i1nn;
private final AxisType m_i2;
private final AxisType m_i2n;
private final AxisType m_i2nn;
private final AxisType m_i3;
private final AxisType m_i3n;
private final AxisType m_i3nn;
private final Vector3D[] m_unitAxis;
public IndexData(
final EulerOrder order )
{
m_i1 = order.getAxisType(0);
m_i2 = order.getAxisType(1);
m_i3 = order.getAxisType(2);
m_i1n = m_i1.nextCircular();
m_i1nn = m_i1n.nextCircular();
m_i2n = m_i2.nextCircular();
m_i2nn = m_i2n.nextCircular();
m_i3n = m_i3.nextCircular();
m_i3nn = m_i3n.nextCircular();
m_unitAxis = order.orderedAxis();
}
public Vector3D V1()
{
return m_unitAxis[0];
}
public Vector3D V2()
{
return m_unitAxis[1];
}
public Vector3D V3()
{
return m_unitAxis[2];
}
public Vector3D V3n()
{
return m_i3n.toVector3D();
}
public AxisType i1()
{
return m_i1;
}
public AxisType i1n()
{
return m_i1n;
}
public AxisType i1nn()
{
return m_i1nn;
}
}
public RotationSequence DecomposeFromQuaternion(
final Quaternion q,
final EulerOrder order )
{
final IndexData d = new IndexData(
order);
final Vector3D v3Rot = q.Rotate(
d.V3()).unit();
Angle theta1 = Angle.Zero();
Angle theta2 = Angle.Zero();
Angle theta3 = Angle.Zero();
if (order.isRepeating())
{
if (order.isCircular())
{
theta1 = Angle.fromRadians(org.apache.commons.math3.util.FastMath.atan2(
v3Rot.at(d.i1n()),
-v3Rot.at(d.i1nn())));
theta2 = Angle.fromRadians(-org.apache.commons.math3.util.FastMath.acos(v3Rot.at(d.i1())));
}
else
{
theta1 = Angle.fromRadians(org.apache.commons.math3.util.FastMath.atan2(
v3Rot.at(d.i1nn()),
v3Rot.at(d.i1n())));
theta2 = Angle.fromRadians(org.apache.commons.math3.util.FastMath.acos(v3Rot.at(d.i1())));
}
if (theta2.radians() < 0)
{
theta2 = theta2.negate();
}
if ((theta2.radians() == 0) || (theta2.radians() == Math.PI))
{
theta1 = Angle.Zero();
}
}
else
{
if (order.isCircular())
{
theta1 = Angle.fromRadians(org.apache.commons.math3.util.FastMath.atan2(
-v3Rot.at(d.i1n()),
v3Rot.at(d.i1nn())));
theta2 = Angle.fromRadians(-org.apache.commons.math3.util.FastMath.asin(-v3Rot.at(d.i1())));
}
else
{
theta1 = Angle.fromRadians(org.apache.commons.math3.util.FastMath.atan2(
v3Rot.at(d.i1nn()),
v3Rot.at(d.i1n())));
theta2 = Angle.fromRadians(-org.apache.commons.math3.util.FastMath.asin(v3Rot.at(d.i1())));
}
}
final Quaternion Q1 = Quaternion.createFromAxisAngle(
d.V1(),
theta1);
final Quaternion Q2 = Quaternion.createFromAxisAngle(
d.V2(),
theta2);
final Quaternion Q12 = Q1.times(Q2);
final Vector3D V3n = d.V3n();
final Vector3D V3n12 = Q12.Rotate(V3n);
final Vector3D V3nG = q.Rotate(V3n);
theta3 = Vector3D.angleBetween(
V3n12,
V3nG);
final Vector3D Vc = Vector3D.crossProduct(
V3n12,
V3nG);
final double m = Vector3D.dotProduct(
Vc,
v3Rot);
final double sign = m > 0 ? 1.0 : -1.0;
theta3 = Angle.fromRadians(sign * org.apache.commons.math3.util.FastMath.abs(theta3.radians()));
return new RotationSequence(
order,
theta1,
theta2,
theta3);
}
}
, (Angle, RotationSequence, Quaternion), , .