Instead of thinking of a rotation as an absolute facing, you can think of it as a change – an offset. In other words, Quaternion.Euler(0,90,0) could mean facing east, or it could mean a quarter circle spin from how-ever you’re facing now.
We can apply a rotation to an arrow, which spins the arrow. Or we can apply a
rotation to another rotation, which adds them together, sort of.
Rotations are applied using a re-purposed star: v1=q*v1; rotates the arrow v1 by
q. Re-using the star is like how we re-use the + to mean push-together in
"cat"+"fish". We’re not actually multiplying the rotation numbers together. We’re
running combine rotation equations. But mathematicians actually call it quaternion
multiplication.
This is some of the hardest stuff, but if you have the basic idea, you can usually trial and error to get an equation working.
To apply a rotation to an arrow, use rotation*arrow. For example, this spins a right-pointing arrow by 90 degrees:
We’re not thinking of y90 as an eastward facing anymore. Now it’s a
free-floating clockwise 90-degree y-spin. It’s like a card in a game that says “turn
right”.
Arrow-rotation works with any kind of spin and any arrow. This finds an arrow to the red cube, then spins it 20 degrees on y:
Imagine the start of the arrow glued to us. The 20 degree spin moves it like a
clock hand. The ball will be the same distance from us as the red cube is. If the cube
was at 4 o’clock, the ball will be at 5 o’clock. It will be at the same height as the red
cube.
Way back in the game board example we started with only the two bottom corners. To get the others we took the arrow along the bottom, pretended it was glued there, and spun it backwards 90 degrees to aim at the top-left corner. We had to use a cheap trick then. Now we can do a real 90 degree spin:
Now lowerLeft + upSideArrow gives us the upper-left corner.
A neat variation of that is making a pentagon. A little math shows the outside bend at each corner is 72 degrees. I’ll start at the lower-left and go counter-clockwise:
I’ll assume we have 5 little cubes to drop at each corner. The code will use the same arrow, twisting it to walk along each new edge:
It’s a little boring, which is the point. The rotating an arrow trick lets us do a
boring turtle-graphics-style walk, using 1 arrow and 1 rotation. Each time we use the
rotation on the arrow, it spins another 72 degrees counter-clockwise.
A fun trick is gradually rotating an arrow. It’s the same as rotating ourself, except with an extra arrow coming out of us. This spins an arrow from forward, clockwise to backwards, repeating. To see if it worked we’ll put a red cube at the tip:
spin*baseArrow is the fun part. It’s an arrow forward, clock-spun 3 degrees, 6
degrees and so on. The tip traces out the right half of a circle, forward to
back.
spin is really a free-floating rotation “slowly turn 180 degrees right”. If we change
baseArrow to Vector3.right; it will spin around the back half. Changing it to
new Vector3(3,0,-1) would spin over whichever weird half-circle started
there.
Some notes on the rules for using these:
We’re really running a function with those two inputs, doing lots of ugly angle math.
In the last chapter LookRotation converted an arrow into a rotation. Sometimes it’s
nice to convert a rotation into an arrow: the (000) rotation is the forwards arrow, and
so on.
The trick is to start with the forward arrow. Using it is like adding the rotation to 0. Quaternion.Euler(0,45,0) is a NorthEast rotation. Quaternion.Euler(0,45,0)*Vector3.forward is an arrow aimed NorthEast.
For any rotation, q*Vector3.forward is an arrow pointed the way q would
aim.
Suppose we need an arrow aimed forwards and right 30 degrees. Normally we’d just write it as (x,0,z), but I don’t know the ratio for 30 degrees. Instead we’ll create that rotation and use it to make the arrow:
Hopefully this is easy to read. We don’t need to imagine the arrow then add that
spin. Hopefully we’ll see Vector3.forward and realize we can think of the spin’s
facing, and that’s the arrow we’ll get.
Unity actually uses this trick to turn your rotation into your forwards arrow. transform.forward is really transform.rotation*Vector3.forward.
For an exercise, suppose we want the red cube to start on our right side, then spin left, up and over making a 1/2-circle above us; then repeat.
Plan #1 is this: create a right arrow as the starting spot. As the picture shows, rotating that arrow around z will bring it up and to the left side:
The code for spinning a rightward arrow around z:
That doesn’t feel too bad. baseArrow is exactly where red starts, and spinning it around z, like a propeller seems obvious enough.
Plan #2 is to make it using pure Euler rotations. Make a rotation that faces right, then leans back up and over to face left. Use Vector3.forward to turn it into an arrow:
If you can look at Quaternion.Euler(degs0to180, 90, 0) and quickly see it as
a right arrow, tilting back on x, then this way is better.
Suppose we want to 1/2-circle the red cube from our personal right to left. We’ll start with the arrow transform.right*2. The end is attached to us, spinning around the long line through our body – around our personal z-axis.
This is the cool part: that line is transform.forward, and we can make a rotation around it using AngleAxis. Here’s the code:
That was a slippery one. The trick is to think of spin as a free-floating rotation.
AngleAxis(0,transform.forward) means no spin, which is no change. Changing 0
to 10 means to take whatever arrow and spin it 10 degrees diagonally around our
forward arrow, and so on.
As I wrote in the intro, sometimes these are trial and error.
Spinning an arrow around itself does nothing. For example spinning Vector3.up around the y-axis is always Vector3.up again. This code does that – the red cube at the tip won’t move:
It works, it spins the arrow, But the tip doesn’t go anywhere. It doesn’t
even have a secret spin. The input arrow is (0,1,0) and the output arrow is
(0,1,0).
This next code also does nothing. It spins our personal forward arrow around itself. The red cube sits there, 3.5 units in front of us:
Hopefully both of these are totally obvious. If you swing a stick, the tip moves. If
you twirl it in place, the tip doesn’t go anywhere.
There’s nothing wrong with spins that don’t move you. It’s not an error. If you
have lots of changing arrows and ways they spin, it makes complete sense that they
sometimes line up for a do-nothing spin.
Spinning funny-angled arrows is like twirling an umbrella and tracking the end of a spoke. If it’s all the way open, the spoke-ends make a big circle. As you close it, they make smaller and smaller circles. The spokes haven’t gotten any shorter. We’re still spinning the entire 2 feet worth of spoke, but the angle gives us tiny circles.
Here’s code for an almost-closed umbrella spin. It takes a long almost-up arrow and spins it around the y-axis:
The red cube is always at y=8, tracing out a small radius 1 circle. It acts like an
almost-folded umbrella spoke.
A long arrow at a small angle spinning in a cone is fine. But all the matters is how the tip moves. The area swept out by the long part doesn’t really matter.
Math-wise, arrows can be broken into the part along the spin arrow, and the part
going straight away from it. That second part is what moves. For example (1,8,0)
around y is like climbing up 8 and staying there; and spinning just (1,0,0) to make a
small perfect circle.
To sum up: the best spins are when the spin axis and the arrow are at 90 degrees. Other angles are fine, but you may have to work harder to visualize how the tip moves.
For a 3D game about fire-hoses, we’d like to shoot little blue balls forward in a small random cone. If we put a wall ahead of us, each ball could hit anywhere within a small circle.
The plan is to start with the forward arrow, which stands for perfect aim. On our aiming circle we’ll pick a 0-360 degree direction to miss in. Then we’ll pick 0-10 degrees for how far we’re off. 0 is the center – we don’t miss at all. 10 means we hit all the way at the edge.
That plan should work, but we need another plan to do it:
Version #1 that won’t work: make a Vector3 with (x,y,10) where x and y are
random -1 to +1. That will give a random mostly forward arrow that can tilt any
direction. But the target area will be square. People will notice when we shoot
enough, so it’s no good.
Version #2 that won’t work: Create an Euler rotation that can aim that way. That won’t work because we can’t make one. We can roll 0-360 on z, but tilting 0-10 on y will only ever go right/left. Or we can compass spin 0-10 on y, but then z-spin will only roll us in place.
That stupid yxz rule is getting us. Quaternion.Euler(x,y,z) can’t spin y then z
like we need.
Version #3: Tilt the forward arrow in 2 steps. First angle it right 0-10 degrees. Second, spin that arrow 0-360 around z. The arrow will trace out a small cone, which is exactly what we want.
The code, in a few parts:
In our mind dirAngle is the actual direction the ball will go. Currently it’s cocked 0-10 degrees right. Next we spin it around z. The tip will trace out a small circle:
dirArrow is now aimed anywhere within a 10-degree forwards cone. To prove it works, we’ll shoot the red cube using it:
Finally, that always shoots forward. Suppose we want to shoot based on the way we’re aiming. That seems like it might be hard, but it’s not. We can rotate the arrow by our rotation:
Later we’ll see a better way to think of this trick. But it’s basically taking an almost-forward arrow, spinning it by us, to get an almost-our-forward arrow.
We can also apply one rotation to another. It uses the star symbol in the same way. You write it like multiplication, but it’s really running rotation math. q1*q2 combines q1 and q2 into one big rotation.
transform.rotation = q1*q2; is completely legal.
But we run into the local/global axis problem we had with Euler angles. Suppose we start rotated q1 and add a 30 degree z spin. Is that on our current z-axis, or the global z axis? The result will be different.
The good part is, we can pick global or local axis. The bad part is, we do it in a semi-confusing way.
When you multiply rotations, the first one is like the start, and the second one spins
it more. It uses the local axis of the first. That’s the entire rule: “second spin starts
with the local axes of the first”. It works just great, but it feels really odd at
first.
To see it work, let’s make 2 simple rotations: a lookAt, and a 360 degree spin around y:
Those two rotations are nothing special. transform.rotation=redLook; aims us at red, and transform.rotation=ySpin; gives us a clockwise flat compass spin.
But combining them gives something we couldn’t do before. It makes us spin in a tilted circle, aimed at red (to see it, red needs to be away from us, and above or below):
Think of it as aiming at red, then purposely adding ySpin after that. It’s only
natural it would spin on the tilted y.
We can use 2 combined rotations to solve the fire-hose cone problem. Spin 0-360 on z, then tilt 0-10 degrees to our current right:
Visualize z360*y0to10 as a cow spinning twice. First z360 rolls it sideways. It’s facing forward, but its right side is spun around to just anywhere. Next it spins 0 to 10 to its personal ride side, which for real could be up, left or any direction.
Breaking it into 2 rotation lets us force the order to be z then local y. We had no
way to do that before.
In Quaternion.Euler(-45,90,0) we know the rule is 90 y first, then -45 on the local x. The word local means the same thing there as it does here. We can break (-45,90,0) into (0,90,0) * (-45,0,0) and it means real y90, local x45:
ySpin90*xSpin45 is just a long way to write Euler(-45,90.0). We
wouldn’t write it out for real, since writing Euler(-45,90,0) is easier in every
way.
For a longer example: as we all know, the Mark-II plasma cannon is mounted on a circular base. To aim, the whole base tips straight back. The scary-looking barrel swings side-to-side like the hand on a tilted clock.
In math terms, it uses an xy order – global x, local y. We can’t make that with Euler. We’ll have to use the combine rotations trick:
If you try this, you can totally see how y is local. At first it’s a normal
side-to-side. But if you tilt back on x, ySpin is in a titled arc. If you’ve ever seen a
real Mark-II plasma cannon, you’ll recognize the distinctive rotation.
This next one is mostly an excuse to use three in a row. We want the cow to lie on its left side, with its back aimed at the red cube. Here’s the plan: aim its face at the red cube; spin it 90 degrees on y – now the left side is aimed there; finally lie down left– now its back is aimed there, with the cow lying on its left side.
In code we make all 3 rotations and combine them in order:
I think having three helps see the local rule. First we aim at red. Then spin right based on that. It’s important we spin on local y so the left side is aiming exactly at red. Then we flop down left, based on how we’re standing now.
Since y beats z, we could have combined them as Euler(0,90,90), but that
would have been harder to read. Facing right, then falling feels like it should be 2
steps.
Here’s a simpler semi-practical one. We’d like an object to z-spin on it’s starting rotation. Without changing it’s aim, it will roll in place. We’ll save the original rotation, create an increasing z-spin, and add it as local:
It feels funny since we’re recomputing our entire rotation from scratch every time. We take our beginning rotation and apply a 2-degree z-spin. Next frame we do the same thing, but with a 4-degree z-spin.
This trick works to take any arrow and give it a fun roll-in-place.
General local rotation combining notes:
We often use the combine rotation trick purposely so we can y-spin on some local axis.
Part of why we like the combine rotations is how simple that makes things.
We can also take a starting rotation and apply to it another as a global. To do that,
the starting rotation goes second, and the one to apply as global goes first:
globalSpin*startingFace.
Let’s start with making a global z-roll. z is normally a local spin, so this should look interesting. We’ll aim at the red cube and spin on z globally, by putting it in front:
Since zDegs* was first, this will not be a nice local roll on z. The arrow to red will
spin around the real z-axis. It will aim it away – far away – from the red cube,
probably spinning in a weird little cone. Each full circle will bring it back to looking
at red. It’s kind of a garbage formula.
You may be thinking “hey wait – you just told me q1*q2 starts with q1 and adds q2 as local. Now you’re telling me it’s q2 adding q1 as global. Which is it?”
Amazingly, it’s both. We’ll just save that bizarre fact for later.
Going back to horrible arrow spins, suppose I have a 3D game where a cannon aims at a target, but also spins in a circle. You have to tap when it’s pointing at the target. It should look like the up/down gear is fine, but the compass gear is running amok. The changes to do that:
This gives us the same junky rotation where the arrow to red spins around crezily,
but now it makes sense. The blown gasket is merry-go-rounding the aim
arrow.
This last one is tricky. We’ve got a flat spinner that tells us which way the wind blows – North, East and so on. On a signal, everything will tip 90 degrees in the wind direction. Everything tips the same way, since this is just general wind – it’s not coming from the spinner.
After some thinking, the wind-blow rotation should be 90 degrees around
the spinner’s personal x-axis. If the spinner was aimed forward, that flops
everything forward – it seems to check out. The math for “90 on spinner’s x” is:
Quaternion.AngleAxis(90, spinner.right);.
Surprisingly, we want that as a global rotation. That’s because we’re using that axis exactly as it is. Local means we want each object to adjust the wind’s x-axis based on their own rotation. That’s clearly wrong. With a global wind-flop, the final code is:
We’re back to the bizarre fact that for rotations, A*B is either A with B added locally, or B with A added globally. Both things are true. It’s one of those crazy math facts.
When you’re creating one, it’s no problem at all. Suppose you have a plan to start
with A, add B on A’s locals, then twist it all using global C. That’s C*A*B (A with B
after and C before).
When you’re trying to read a compound rotation, it’s still not so bad. Think of
having two ways to read it, and only one needs to make sense.
Take lookAtRed*zRoll. That can be looking at red first, then doing a z-roll on the local axis. Local z-rolls don’t change how we aim, so that feels pretty good. Or it’s z-roll first: we start aimed North, rolling in place. Then lookAtRed, as a global, twists us to look at red, taking the z-roll with it.
Consider the left-lying back-facing cow: toRed*turnRight*lieOnLeft. In this we should start with turnRight*lieOnLeft. Those take a 000 rotation cow and set it up – it’s looking East, lying on its side. That’s easy to visualize. Then we apply toRed as a global, spinning the whole thing in-place to face the red cube.
That’s a useful pattern. Whenever a look-at comes first, think of it as a
global. It applies last. Whatever you made with the rest, it gets aimed at
red.
From before we know z360*y0to10 makes a rotation aimed in a small forward cone. One way to read it: 360 spin on z, which does nothing yet. Then the random 0-10 local y-tilt, which could go in any direction of the cone. Or read it as: random 0-10 degree tilt right, followed by a global z-spin which traces out the cone.
The last step of the old cone example was making it face the way we do. That’s
another example of the “look-at goes first” pattern. transform.forward*z360*y0to10
is a global transform.forward aiming the forward cone.
For fun, we can break Quaternion.Euler(45,20,180) into all 3 parts. The
official order is always yxz, so we can write it as y20*x45*z180. We’re allowed to
think of that as x first, local z, then global y. Or any order as long as it follows the
local/global based on how it was written.
Summary, notes: