Prerequisites
Definition of terms:
Equations
angle = acos ( ( m00 + m11 + m22 - 1)/2)
x = (m21 - m12)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2)
y = (m02 - m20)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2)
z = (m10 - m01)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2)
There are two singularities at angle = 0° and angle = 180°, in
these cases the above formula may not work (as pointed out by David)
so we have to test for these cases separately. At 0° the axis is arbitrary
(any axis will produce the same result), at 180° the axis is still relevant
so we have to calculate it.
Derivation of Equations
I can think of two methods to derive the equations:
I have used both of these and so that I don't interrupt the flow I have put them on
this page.
Singularities
So how can we find this case? When we look at the formula for AxisAngle
to Matrix the asymmetrical component about the leading diagonal is due to
the sin(angle) component.
So when sin(0)=sin(180) = 0
Then the matrix [m]t = [m]
So if we test for symmetry:
Abs(m01-m10)<0.001 and Abs(m02-m20)<0.001 and Abs(m12-m21)<0.001
Then
Angle = 0 or 180°
The angle = 0 case should be easy to find because the matrix will be:
1 0 0
0 1 0
0 0 1
So we can test for this case with:
Abs(m01+m10)<0.001 and Abs(m02+m20)<0.001 and Abs(m12+m21)<0.001
At 180° we need to calculate the axis, to do this we go back to the
formula for AxisAngle to Matrix
c = cos(180) = -1
s = sin (180) = 0
t = 1-c = 2
so the matrix is:
| 2*x*x-1 |
2*x*y |
2*x*z |
| 2*x*y |
2*y*y -1 |
2*y*z |
| 2*x*z |
2*y*z |
2*z*z*-1 |
so using the leading diagonal terms,
x = +or- sqrt((m00+1)/2)
y = +or- sqrt((m11+1)/2)
z = +or- sqrt((m22+1)/2)
So we need to use the non-leading-diagonal terms to work out whether they are
positive or negative, the permutations of positive, negative and zero values
of x,y and z are:
| x |
y |
z |
2*x*y |
2*x*z |
2*y*z |
Notes |
| + - |
+ - |
+ - |
+ |
+ |
+ |
Use positive |
| |
|
|
+ |
+ |
- |
Not possible |
| |
|
|
+ |
- |
+ |
Not possible |
| |
|
|
- |
+ |
+ |
Not possible |
| + - |
+ - |
- + |
+ |
- |
- |
Use -z |
| + - |
- + |
+ - |
- |
+ |
- |
Use -y |
| - + |
+ - |
+ - |
- |
- |
+ |
Use -x |
| |
|
|
- |
- |
- |
Not possible |
| |
|
0 |
+- 1 |
0 |
0 |
|
| |
0 |
|
0 |
+- 1 |
0 |
|
| 0 |
|
|
0 |
0 |
+- 1 |
|
| |
|
|
0 |
0 |
0 |
Rotation around an axis. Use positive 1 |
| 0 |
+ - |
+ - |
0 |
0 |
+ |
Use positive |
| + - |
0 |
+ - |
0 |
+ |
0 |
Use positive |
| + - |
+ - |
0 |
+ |
0 |
0 |
Use positive |
| 0 |
+ - |
- + |
0 |
0 |
- |
Use -z (can be -y or -z) |
| + - |
0 |
- + |
0 |
- |
0 |
Use -z (can be -x or -z) |
| - + |
+ - |
0 |
- |
0 |
0 |
Use -x (can be -x or -y) |
key:
- + means a positive value
- - means a negative value
- 0 means very close to zero (within epsilon)
where two sets of values give the same result they are shown on the same line
for instance:
means x and y have same sign and z has opposite sign.
Note we can only distinguish 4 non-zero combinations from x*y , x*z and y*z,
for instance +x,+y,+z gives the same matrix as -x,-y,-z this is because rotation
by +180 degrees produces exactly the same rotation as -180° and we don't
know which way it went round unless we happen to know any intermediate values.
Where we have to be careful is when x, y or z are close to zero and so a small
error might flip between + and -. If x*y , x*z and y*z are all zero then we
are rotating purely about one axis, the sign in this case is not important.
If one of them is zero then we may have to invert one of the others as shown
in the table above.
So the algorithm emerging is as follows:
if (matrix symmetrical) {
if (identity matrix) return zero angle
// this is 180 deg rotation
calculate axis with positive values
if (x is zero and y,z are not zero) invert y or z
else if (y is zero and z is not zero) invert x or z
else if (z is zero) invert x or y
else if (x*y , x*z and y*z are all positive) return positive values
else if (y*z is positive) invert x
else if (x*z is positive) invert y
else if (x*y is positive) invert z
}
Java Code
/**
This requires a pure rotation matrix 'm' as input.
*/
public axisAngle toAxisAngle(matrix m) {
double angle,x,y,z; // variables for result
double epsilon = 0.01; // margin to allow for rounding errors
// optional check that input is pure rotation, 'isRotationMatrix' is defined here:
// http://www.euclideanspace.com/maths/algebra/matrix/orthogonal/rotation/
assert isRotationMatrix(m) : "not valid rotation matrix" ;// check during debugging only
if ((Math.abs(m[0][1]-m[1][0])< epsilon)
&& (Math.abs(m[0][2]-m[2][0])< epsilon)
&& (Math.abs(m[1][2]-m[2][1])< epsilon)) {
// singularity found
// first check for identity matrix which must have +1 for all terms in leading diagonal
// and zero in other terms
if ((Math.abs(m[0][1]+m[1][0]) < 0.1)
&& (Math.abs(m[0][2]+m[2][0]) < 0.1)
&& (Math.abs(m[1][2]+m[2][1]) < 0.1)
&& (Math.abs(m[0][0]+m[1][1]+m[2][2])-3) < 0.1) {
// this singularity is identity matrix so angle = 0
// note epsilon is greater in this case since we only have to distinguish between 0 and 180 degrees
return new axisAngle(0,1,0,0); // zero angle, arbitrary axis
}
// otherwise this singularity is angle = 180
angle = Math.PI;
x = (m[0][0]+1)/2;
if (x > 0) { // can only take square root of positive number, always true for orthogonal matrix
x = Math.sqrt(x);
} else {
x = 0; // in case matrix has become de-orthogonalised
}
y = (m[1][1]+1)/2;
if (y > 0) { // can only take square root of positive number, always true for orthogonal matrix
y = Math.sqrt(y);
} else {
y = 0; // in case matrix has become de-orthogonalised
}
z = (m[2][2]+1)/2;
if (z > 0) { // can only take square root of positive number, always true for orthogonal matrix
z = Math.sqrt(z);
} else {
z = 0; // in case matrix has become de-orthogonalised
}
boolean xZero = (Math.abs(x)<epsilon);
boolean yZero = (Math.abs(y)<epsilon);
boolean zZero = (Math.abs(z)<epsilon);
boolean xyPositive = (m[0][1] > 0);
boolean xzPositive = (m[0][2] > 0);
boolean yzPositive = (m[1][2] > 0);
if (xZero && !yZero && !zZero) { // implements last 6 rows of above table
if (!yzPositive) y = -y;
} else if (yZero && !zZero) {
if (!xzPositive) z = -z;
} else if (zZero) {
if (!xyPositive) x = -x;
}
return new axisAngle(angle,x,y,z);
}
double s = Math.sqrt((m[2][1] - m[1][2])*(m[2][1] - m[1][2])+(m[0][2] - m[2][0])*(m[0][2] - m[2][0])+(m[1][0] - m[0][1])*(m[1][0] - m[0][1])); // used to normalise
if (Math.abs(s) < 0.001) s=1; // prevent divide by zero, should not happen if matrix is orthogonal and should be
// caught by singularity test above, but I've left it in just in case
angle = Math.acos(( m[0][0] + m[1][1] + m[2][2] - 1)/2);
x = (m[2][1] - m[1][2])/s;
y = (m[0][2] - m[2][0])/s;
z = (m[1][0] - m[0][1])/s;
return new axisAngle(angle,x,y,z);
}
thanks to Vladimir Smutny for the correction and Andreas Keil here.
Issues
Examples
In order to check if the axis is reversed when we have a minus angle
here are 2 examples, example 1 represents + 30° about the z axis
and example 2 represents - 30 degrees about the z axis, so these examples
should produce either, equal and opposite angles, or equal and opposite
axes.
Example 1
In this example heading is + 30 degrees about the z axis.
| matrix = |
| cos(heading) = 0.866 |
sin(heading) = 0.5 |
0 |
| -sin(heading) = -0.5 |
cos(heading) = 0.866 |
0 |
| 0 |
0 |
1 |
|
angle = acos ( ( m00 + m11 + m22 - 1)/2)
angle = acos ( ( 0.866 + 0.866 + 1 - 1)/2)
angle = acos ( 0.866 )
angle = 30 degrees
x = (m21 - m12) = 0 - 0 =0
y = (m02 - m20) = 0 - 0 =0
z = (m10 - m01) = -0.5 - 0.5 = -1
example 2
now the same example with heading = -30 degrees about the z axis.
| matrix = |
| cos(heading) = 0.866 |
sin(heading) = -0.5 |
0 |
| -sin(heading) = 0.5 |
cos(heading) = 0.866 |
0 |
| 0 |
0 |
1 |
|
angle = 30 degrees
x = (m21 - m12) = 0 - 0 =0
y = (m02 - m20) = 0 - 0 =0
z = (m10 - m01) = 0.5 + 0.5 = 1
So reversing the angle reverses the axis instead of the angle. However
these are equivalent (we can reverse both the angle and axis and still
represent the same rotation). So this is equivalent to -30° about
z = -1 axis.
This gives me some confidence that whatever quadrant the rotation is
in the above formula will convert this correctly to the correct axis angle.
example 3
| matrix = |
| 0.96608673169969 |
-0.25800404198456 |
-0.01050433974302 |
| 0.25673182392846 |
0.95537412871306 |
0.14611312318926 |
| -0.02766220194012 |
-0.14385474794174 |
0.98921211783846 |
|
angle = acos ( ( 0.966 + 0.955 + 0.989 - 1)/2)
angle = acos (0.955336489125605)
angle = 17.3 degrees
x = (m21 - m12) = -0.14385474794174 - 0.14611312318926 = -0.289967871131
y = (m02 - m20) = -0.01050433974302 + 0.02766220194012 = 0.0171578621971
z = (m10 - m01) = 0.25673182392846 + 0.25800404198456 = 0.51473586591302
This axis can then be normalised if required.
Here I am not sure that the matrix represents a pure rotation? The matrix
is not skew symmetric?
Specially m02 and m20 as they are both small and have the same sign.
This leads me to the question about how sensitive the conversion is to
input errors? Perhaps a small error here might put the axis into the wrong
quadrant. And this error would be increased when the axis is normalised.
If the original matrix may not be orthogonal then, perhaps we should use
a different conversion method, depending on the ratio of values in the
leading diagonal as we did here: matrix
to quaternion page. |
Another Example
| we take the 90 degree rotation from this: |
 |
to this: |
 |
As shown here
the matrix for this rotation is:
So using the above result:
angle = acos ( ( m00 + m11 + m22 - 1)/2) = acos (0) = 90 degrees
x = (m21 - m12)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2) = 2 / sqrt (4) = 1
y = (m02 - m20)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2) = 0
z = (m10 - m01)/sqrt((m21 - m12)2+(m02 - m20)2+(m10 -
m01)2) = 0
angle = 90°
axis = 1,0,0
As you can see here, this gives the
result that we are looking for.
Angle Calculator and Further examples
I have put a java applet here which allows the values to be entered and the converted values shown along with a graphical representation of the orientation.
Also further examples in 90° steps here
This site may have errors. Don't use for critical systems.