Where you are. You can build a 2D rotation matrix from trigonometry and read one by its columns. This lesson takes that machinery into three dimensions, where the columns rule survives completely intact and one genuinely new thing appears.
Three knobs on a tripod
Borrow a photographer’s geared tripod head, the kind with three separate knobs rather than one ball joint. Put a camera on it and work each knob on its own, watching through the viewfinder.
The bottom knob sweeps the camera across the horizon. The view pans left and right. The horizon stays perfectly level and the camera keeps looking at the same height.
Lock that one and try the middle knob. Now the camera nods, tipping up toward the ceiling and down toward the floor. The horizon is still level. The camera still faces the same compass direction it did before.
Lock that and try the third. Nothing about where the camera is aimed changes at all; it stares at the same spot on the wall. What happens is that the horizon in the viewfinder tips over.
Three knobs, and each one leaves something completely alone. What each one leaves alone is a line running through the camera. Every other direction in the scene swings; that one line does not budge. And whatever orientation you eventually want, you get there by working the three knobs in some order, one at a time.
The idea in one paragraph
A rotation in 3D does exactly what a rotation in 2D did: it sends the unit axes somewhere, and column i of the matrix is where axis i landed. The new fact is that a 3D rotation has an axis, a direction it leaves untouched, and the three simplest rotations are the ones whose axis is one of the coordinate axes. Each of those three is the identical 2×2 block you derived last lesson, sitting inside a 3×3 with a 1 parked on the untouched axis. You combine them by multiplying, and the rightmost matrix is the one that acts first. The product of two rotations is always another rotation, which means the honest rotations form a closed set; that set is called SO(3), and unpacking the name tells you the whole story: nine numbers, six constraints, three real degrees of freedom.
The rotation with a 1 in the corner
Turn about the z-axis. Whatever is happening, z itself does not move, and x and y turn within their own plane exactly as they did in 2D. So the matrix writes itself. Take last lesson’s 2×2 block, put it in the x and y rows and columns, and park a 1 where z maps to itself:
In words: x and y turn into each other by ; z is left alone. Read it by columns, the same as before. At the entries collapse to integers, and the columns say , , . Right went to up, up went to left, and z sat still.
The other two are the same matrix wearing different labels
Now do the same for x. Turning about x leaves x alone, and y and z turn into each other. And for y: y is left alone, and z and x turn into each other.
Wider than the screen; scroll it sideways.
Notice the pattern in the diagram, because it is doing real work. The pairs are not arbitrary. They follow the cycle x → y → z → x. turns the pair (x, y). turns the next pair along, (y, z). turns the one after that, (z, x).
| Rotation | Leaves alone | Turns the pair | First axis goes to |
|---|---|---|---|
| x | (y, z) | y toward z | |
| y | (z, x) | z toward x | |
| z | (x, y) | x toward y |
Written out in the usual x, y, z row-and-column order, the three matrices are:
Which one happens first
Combine rotations by multiplying them. The convention is that means “do , then ,” so the rightmost matrix acts first. That looks backwards until you see where it comes from, and the columns rule gives it to you in one line.
Column i of the product equals applied to column i of . That is simply how matrix multiplication is defined. Read it as a sentence: take where R1 sent axis i, then apply R2 to that. Axis i meets first. So the rightmost matrix acts first, not by decree but because that is what the arithmetic does.
Wider than the screen; scroll it sideways.
The diagram works a concrete case: , one row per axis. The z-axis is the interesting one. sends it to , and then sends that to . Stack the three results side by side and you have written down the product matrix without ever multiplying two matrices together.
Order is now load-bearing
In 2D, , and addition does not care about order. In 3D that shortcut is gone, because two rotations about different axes are not a single rotation about a shared axis, so there is no single number left to add.
The smallest demonstration: take the x-axis and apply . It ends up at . Swap the two and apply to the same starting axis, and it ends up at . Same two turns, same starting vector, two different answers, a full quarter turn apart.
This deserves its own lesson and gets one; why rotation order matters makes you feel it rather than read it. Hold onto the consequence: the order of a sequence of rotations is part of the data, and a convention that leaves it unstated is a bug waiting to be filed.
A rotation of a rotation is a rotation
Here is the property that makes any of this safe to build on. Multiply two rotations and you always get a rotation. Both checks from last lesson survive the product, and the proofs are one line each.
Orthonormality: , using nothing but .
Determinant: determinants multiply, so .
So you can chain as many turns as you like and never fall out of the set. Add that the identity matrix is a rotation of zero, and that every rotation has an inverse inside the set (its transpose), and you have a closed system: compose, invert, compose again, and you are always holding something valid.
Nine numbers, three freedoms
A rotation matrix holds nine numbers, but you cannot choose them freely. Count the constraints: three columns must each have length 1, and three pairs of columns must each be perpendicular. Six equations. Nine numbers minus six constraints leaves three.
Wider than the screen; scroll it sideways.
The requirement is a further condition, but a different kind of one. Orthonormality already pins the determinant to or , so requiring is not a seventh equation shaving off another dimension; it is a choice between two separated halves, rotations or mirrors. That is why the count comes out at three and not two.
Three is the honest size of an orientation, and it explains two things you will meet repeatedly.
First, it is why schemes with three angles are so tempting. Three numbers is exactly the right budget, so surely three angles will do. They very nearly do, and euler angles and gimbal lock is the lesson about the bill.
Second, it is why you cannot simply predict nine numbers and call them a rotation. Nine floats land inside SO(3) essentially never. Drawing 200,000 random 3×3 matrices from a normal distribution produces exactly zero that come within 0.05 of being orthonormal; the set of rotations is a three-dimensional sliver inside a nine-dimensional space, and nothing lands on it by accident.
The three knobs were not special
One last fact, which closes the loop back to the tripod. Multiply any sequence of axis rotations together and the result is always a single rotation about a single axis by a single angle.
Take a concrete stack: . Three separate turns about three different axes. The product is one turn of 80.1693° about the axis , and rebuilding the matrix from that axis and that angle reproduces the original to 2.2e-16.
So the three knobs on the tripod head were only ever a convenient way of reaching an orientation. The orientation itself was always one turn about one line. That is why three numbers is the right budget, and it is the fact that quaternions will eventually cash in.
Check yourself
1. Read Rz(90°) = [[0, −1, 0], [1, 0, 0], [0, 0, 1]] by its columns. What does each column tell you?
Column one is : the x-axis landed on +y. Column two is : the y-axis landed on −x. Column three is : the z-axis did not move, which is the definition of z being the axis of this rotation. Right to up, up to left, z untouched.
2. Why does Ry appear to have its minus sign in the wrong corner?
It does not have one. turns the pair , in that order, because the cycle is x → y → z → x. Written with its rows and columns in the order it is the plain 2D block , identical to the other two. The apparent relocation of the minus sign is entirely an artefact of insisting on sorting rows and columns as x, y, z. Nothing about the rotation is different.
3. R = R2 @ R1. Which rotation happens first, and can you justify it from the columns rule rather than by convention?
happens first. Justification: column i of is applied to column i of . Column i of is where sent axis i, so the axis meets before it meets . The rightmost matrix touches the axes first. This is arithmetic, not a naming choice.
4. Why is the product of two rotations always a rotation? Two facts, one line each.
Orthonormality survives: . And the determinant survives, because determinants multiply: . Together they mean you can compose as long as you like and never leave SO(3), which is what makes chaining frames safe.
5. A model predicts orientation as nine raw floats. What goes wrong, and what do you do about it?
Nine unconstrained floats are almost never a rotation. SO(3) is a three-dimensional set living inside a nine-dimensional space, so random or slightly-noisy outputs land off it essentially always: 200,000 random Gaussian 3×3 matrices produced zero within 0.05 of orthonormal. The repair is to project back with an SVD, using and flipping the last column of if the determinant comes out negative. The deeper fix is to have the model predict something with fewer redundant numbers in the first place.
6. What does the word “special” buy you in “special orthogonal”?
It rules out mirrors. Orthogonal alone means , which allows a determinant of either or ; the cases reverse handedness and cannot be reached by physically turning anything. “Special” pins the determinant to . It removes no degree of freedom, since it selects between two separated halves rather than constraining a continuous quantity, which is why an orientation still has three degrees of freedom and not two.
Do this
Open code/rotations_lab.py and do the first two parts. Twenty minutes.
1. Write the three matrices. Fill in the Rx, Ry and Rz stubs using nothing but numpy. Derive each one rather than looking it up: name the axis it leaves alone, put the 1 there, and drop the 2×2 block into the other pair. Then run part1_properties(). It composes a hundred random triple products and asserts that each is a genuine rotation, using the is_rotation helper already in the file, and it cross-checks your Rz against scipy. If you got a sign wrong, one of those two assertions catches it immediately; a sign error is the only realistic mistake here, and both checks are designed to find it.
2. Break commutativity yourself. Run part2_order_matters(), which applies and to the x-axis and prints both. You should get and . Before you look at the output, predict both using only the columns rule and the diagram above; getting the prediction right is the actual exercise, and the print statement is just the marker.
Then, on paper: pull the sub-block of your Ry on indices [2, 0], in that order, and confirm by eye that it is the plain 2D rotation matrix. That is the minus-sign gotcha, retired for good.
part3_double_cover() and part4_slerp_vs_euler() in the same file belong to quaternions; leave them alone for now. The finished matrices, with the reasoning as comments, are in solutions/rotations_lab.py once yours runs.
What you can now do
You can write , and from first principles instead of memory, explain why ‘s minus sign only looks displaced, compose rotations in the correct order and justify that order from the columns rule, and prove that composing rotations never produces anything but a rotation. You can say what each word in “special orthogonal group” is doing, count why an orientation has three degrees of freedom rather than nine, and repair a matrix that is nearly-but-not-quite a rotation.