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
%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.
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))
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.
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))
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))
RM = matrix(SR,[Uv, Vv, Wv])
RMt = RM.transpose()
RMRMt = RM * RMt
pretty_print("R = ", RM.n(prec=20))
pretty_print(RM.n(prec=20), RMt.n(prec=20), " = ", RMRMt.n(prec=10))
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.
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))
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.
RT = RMt * RMz * RM
pretty_print("RT = ", RT.n(prec=20))
Now let us plot before and after version of a 3D object put through the axis-angle rotation
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))
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)
Much of the value in this notebook is to enter different axis-angle combinations and see what happens