Axis-Angle Rotation

This notebook illustrates how to generate a rotation matrix based upon and axis-angle specification. In addition to being of practical value, understanding this construction is a great start for understanding how cameras may be placed in the world.

A minor point. To keep the examples below a bit simpler, we are not doing this derivation in homogenous coordinaes. Everything you see behaves equivalently, but of course all the matrices would be 4x4 instead of 3x3.

Ross Beveridge
August 30, 2018

In [25]:
%display latex
latex.matrix_delimiters(left='|', right='|')
latex.vector_delimiters(left='[', right=']')

To begin recognize that in specifying an axis angle rotation we begin with a direction w and an angle theta.

In [26]:
Wv    = vector([1, 0, 0])
Wv    = (1/Wv.norm())*Wv
angle = 90
pretty_print("W = ", Wv.n(prec=20), " and ", LatexExpr(r" \;\;\theta = "), angle.n(prec=20))

Rotate so theta becomes the z-axis

Using good old trigonmetry we would know easily how to rotate an amount theta about the z-axis. This fact begs the question: how can we make W the z-axis. The answer is build a rotation matrix where W becomes our z-axis.

In other words, the unit length version of W is the z-axis and will be placed in the third row of a 3x3 rotation matrix. What must happen to fill out the matrix is we need two other unit length vectors mutually orthongonal to W. To do this, we carry out a several step process.

First, we find a vector M that is not parallel to W. Note, that is all we care about and nothing more. The cross product of W and this vector M will define the x-axis after application of the rotation matrix we are constructing.

Second, now that the top and bottom row of our rotation matrix is defined, the middle row is essentially define since it must be mutually orthongonal to the U and W vectors. Therefore, W cross U yeilds the middle row of the rotation matrix.

All these steps are carried out in the code that follows.

In [27]:
Mv = copy(Wv)
for i in range(3) :
    if (Mv[i] == min(Wv)) :
        j = i
Mv[j] = 1.0        
pretty_print("M = ", Mv.n(prec=20))    
In [28]:
Uv = Wv.cross_product(Mv)
Uv = (1/Uv.norm())*Uv 
Vv = Wv.cross_product(Uv)
pretty_print("U = ", Uv.n(prec=20))
pretty_print("V = ", Vv.n(prec=20))
In [29]:
RM    = matrix(SR,[Uv, Vv, Wv])
RMt   = RM.transpose()
RMRMt = RM * RMt
pretty_print("R = ", RM.n(prec=20))
In [30]:
pretty_print(RM.n(prec=20), RMt.n(prec=20), " = ", RMRMt.n(prec=10))

Rotate by theta about the z-axis

The 3x3 rotation matrix needed to carry out the rotation is created by using a 2x2 rotation matrix in the x and y positions of a 3x3 matrix leaving the last row and last column as they would appear in an identity matrix.

In [31]:
Re   = RealField(100)
arad = (angle / 180) * Re(pi)
ca  = cos(arad)
sa  = sin(arad)
RMz = matrix.identity(SR,3)
RMz[0,0] = ca; RMz[0,1] = -sa
RMz[1,0] = sa; RMz[1,1] =  ca
pretty_print("Rz = ", RMz.n(prec=20))

Put the 2 parts together

It is best to think in terms of a three part process. First rotation so that the z-axis becomes the axis about which rotation is to take place. Next, apply that rotation. Finally, reverse the original rotation. You may have to think about this final 'reversal' step but indeed it accomplishes what we desire and the final result of multiplying 3 matrices is a single new rotation matrix that transforms all points in accordance with the definition of an axis-angle rotation.

In [32]:
RT = RMt * RMz * RM
pretty_print("RT = ", RT.n(prec=20))

See the rotation in action

Now let us plot before and after version of a 3D object put through the axis-angle rotation

In [33]:
elA = matrix([[1,1],[3,1],[3,2],[2,2],[2,4],[1,4]])
elA = elA.augment(matrix(ZZ,6,1, lambda x,y:0))
elA = elA.transpose()
elB = RT * elA
pretty_print("Before: Pts = ", elA)
pretty_print("After:  Pts = ", elB.n(prec=20))
In [34]:
ptsA = list(elA[0:3,:].transpose())
ptsB = list(elB[0:3,:].transpose())
gelA = polygon3d(ptsA,color='green')
gelB = polygon3d(ptsB,color='orange',alpha=0.5)
bnd = 5
xaxes = arrow3d([0,0,0],[bnd,0,0],color='red')   + line3d([[0,0,0],[-bnd,0,0]],color='red')
yaxes = arrow3d([0,0,0],[0,bnd,0],color='green')  + line3d([[0,0,0],[0,-bnd, 0]],color='green')
zaxes = arrow3d([0,0,0],[0,0,bnd],color='blue') + line3d([[0,0,0],[0,0, -bnd]],color='blue')
gos = gelA + gelB + xaxes + yaxes + zaxes
gos.show(xmin=-bnd, ymin=-bnd, xmax=bnd, ymax=bnd, aspect_ratio=1)

Go back and try different rotations

Much of the value in this notebook is to enter different axis-angle combinations and see what happens