Math for Game Development and WebGL Part 5: Rotating a Vector

Now that we know how to find the x and y position of an angle, can we just take an angle and draw our points there? Not quite. Any given point already has an angle, as you can see here:

The point (4, 3) already has the angle shown as α. Now what we could do is find the angle of this point, add our new angle to that angle, find the position of that new angle in the unit circle, then scale the vector back out by the magnitude of the original vector. And this actually does work. You can find the angle of a point by solving for the angle in a sine/cosine/tangent expression.  If we remember soh-cah-toa, tangent of an angle is opposite over adjacent, so:

tangent of an angle = 3 / 4
tangent of an angle = 0.75

If we could undo the tangent on both sides we could solve for the angle. This can be done with inverse operations arcsine, arccosine and arctangent. So we the angle is:

arctangent of tangent of angle = arctangent of 0.75
angle = arctangent of 0.75
angle ≈ 0.64

Then if we want to rotate by one radian, our new angle would be 1.64. Awesome, we already know how to rotate by an angle, and now we have our new angle! We can then use sine and cosine to find the position of that angle in the unit circle. Our x and y are then only in the unit circle, between -1 and 1, so we need to scale it back out by the length of the vector, which we can do by multiplying by our magnitude.

Hopefully this makes sense after what we’ve learned previously about sine and cosine. Multiplying cosine by the hypotenuse gives you the y position, and multiplying sine by the hypotenuse gives you the x position.

You can actually see an example of this working:

See the Pen
Vector2 with Inefficient Rotation
by Rob Louie (@rlouie)
on CodePen.

You don’t need to deeply understand arctangent, just know that we use it to get the angle. We then add the angles to get the new angle of the vector. We then get the sine and cosine of that angle and multiply them by the magnitude of the vector to get the new x and y positions. In order to understand the next part it is very important to understand that.

This is the intuitive way to do this, but it is not the way rotation is done in computer graphics. It doesn’t fit the pattern of matrix math, meaning it’s going to be less efficient and more difficult to deal with overall (we’ll learn matrix math soon). Instead rotation makes use of two far less intuitive formulas called the angular addition identity for sine and the angular addition identity for cosine. Those formulas are as follows:

cos(originalAngle + angleToRotate) = cos(originalAngle) * cos(angleToRotate) - sin(originalAngle) * sin(angleToRotate)
sin(originalAngle + angleToRotate) = sin(originalAngle) * cos(angleToRotate) + cos(originalAngle) * sin(angleToRotate)

There’s a lot going on there, but they can be simplified since we know the x and y of the original angle. Much like in our more intuitive example, the sine and cosine of our added angles will just get us the positions in the unit circle.  However, we want the actual new vector position, so we need to multiply by the length of the vector, just like our previous example. Lets look at that:

vector.magnitude() * cos(originalAngle + angleToRotate) = vector.magnitude() * cos(originalAngle) * cos(angleToRotate) - vector.magnitude() * sin(originalAngle) * sin(angleToRotate)
vector.magnitude() * sin(originalAngle + angleToRotate) = vector.magnitude() * sin(originalAngle) * cos(angleToRotate) + vector.magnitude() * cos(originalAngle) * sin(angleToRotate)

I’ve put the changes in green. All I’ve done is taken us outside the unit circle by multiplying the the magnitude of the vector, just like we did in our more intuitive example.

Lets think back to what we’ve learned and what we did in the more intuitive example. Once we found both angles and added them together, we got the cosine of that angle and then multiplied by the magnitude. That got us the new x position. We did the same thing with sine to get the y position. That means that the cosine of the summed angles times the magnitude is the new x position. And that the sine of the summed angles times the magnitude is the new y position. Let’s substitute that in now.

newXPosition = vector.magnitude() * cos(originalAngle) * cos(angleToRotate) - vector.magnitude() * sin(originalAngle) * sin(angleToRotate)
newYPosition = vector.magnitude() * sin(originalAngle) * cos(angleToRotate) + vector.magnitude() * cos(originalAngle) * sin(angleToRotate)

Using the same logic that the cosine of an angle of a vector times a vector’s magnitude is the vector’s x position, and the sine of the angle of a vector times a vector’s magnitude is it’s y position, we can also substitute those in our equation:

newXPosition = vector.x * cos(angleToRotate) - vector.y * sin(angleToRotate) 
newYPosition = vector.y * cos(angleToRotate) + vector.x * sin(angleToRotate)

All I did was replace any instance of the magnitude times the cosine of the original angle with x, and any instance of magnitude times the sine of the original angle with y. This is now our formula for rotation, but why does it work?

Why it works

To show how it works, lets deal with just two angles. Note that for simplicity, the line from A to B (aka line AB) has a length of one:

As you recall from the post about the unit circle and sine/cosine, this just makes the sine of x the opposite side, since the hypotenuse is one.

I want to prove that adding them together satisfies the angular addition identity for sine. Since we just want to prove this, lets be very explicit and make these right triangles:

So lets recall our angular addition identity for sine:

sin(originalAngle + angleToRotate) = sin(originalAngle) * cos(angleToRotate) + cos(originalAngle) * sin(angleToRotate)

Lets go ahead and substitute in our angle names here:

sin(x + y) = sin(x) * cos(y) + cos(x) * sin(y)

So first lets draw a line for the opposite side of the triangle formed from both angles combined together:

The sine of x and y added together is the length of our new line EF. Lets go ahead and write that out:

sin(x + y) = length of BG

To clarify, the points A, B and G form a single large triangle whose angle is x + y. Since the hypotenuse of that large triangle is still one, the sine of x + y is the length of line BG.

Now I’m going to split the line EF with a new line coming horizontally from point C:

 

I’ve drawn the line CH over so that it splits BG into two different lines, BH and HG. If we add those together, we still get the original line EF, so lets write that out:

sin(x + y) = length of BH + length of HG

Now notice that line HG and line CF are the same length. G, H, C, and F form a rectangle with the X axis, so both sides are the same.

So we could rewrite our equation now as:

sin(x + y) = length of BH + length of CF

Now we have to figure out some things about our bottom triangle. Notice that the hypotenuse of our bottom triangle is the adjacent side of our top triangle. Can we figure out the adjacent side?

Well, cosine of x is adjacent over hypotenuse, and hypotenuse is one, so cosine of x is the adjacent side. Or, flipped around, the adjacent side is the cosine of x!

Now we want to find the length of line CF. We know the length of line CF is the sine of y. Sine is opposite over hypotenuse, and the hypotenuse is cos(x) so:

sin(y) = length of CF / cos(x)

We can solve for CF by multiplying both sides by cos(x) and get:

length of CF = sin(y) * cos(x)

Lets substitute that into our original equation where length of CF was:

sin(x + y) = length of BH + sin(y) * cos(x)

Alright we’re halfway there to building back the original equation. How do we now get BH in the same format as our original equation. To do that we need to learn some things about the triangle formed by B, H, and C.

We can actually also learn some things about the angles inside this triangle due to how angles of transversals work in parallel lines:

 

This may or may not be something you already know, but when a line intersects two parallel lines, the opposite angles in each line are the same, and those angles are the same in both parallel lines, as shown in the image above.

With that in mind, we can actually figure out some angles as they relate to y. Since line CH is parallel to the x-axis and line AC intersects both, we can use the same logic to determine that this angle is also y:

 

We know this because it’s the opposite side of angle x on our top triangle there. We also know that the angle formed by lines BC and CA form a right angle, so that corner of our BHC triangle is 90° – y (yes I’m using degrees instead of radians here for simplicity’s sake, there’s enough other things to worry about here).

Now, the angle formed by BHC is a 90° angle, because we’re in a right triangle. We also know that the angles of a triangle add up to 180°. So can we figure out the angle of HBC? That angle must also be y:

If that isn’t intuitive, do the math:

90 + 90 - y + y = 180

The y and -y cancel out and we are left with 90 + 90, which does in fact equal 180.

We just need one more thing, the hypotenuse of this triangle. Luckily it’s also the opposite side of the triangle ABC, meaning it’s sin(x)

As a reminder, here’s where we left our formula:

sin(x + y) = length of BH + sin(y) * cos(x)

We need to find the length of BH. Well BH is adjacent to angle y. Cosine of angle y will give us adjacent over hypotenuse, so lets solve for adjacent:

cos(y) = length of BH / sin(x)

We can multiply both sides now by sin(x) to solve for BH:

length of BH = cos(y) * sin(x)

Okay, lets substitute that into our original equation:

sin(x + y) = cos(y) * sin(x) + sin(y) * cos(x)

We are now back to our original equation. Due to how we solved it, the order is a little different, but remember that multiplication can be done in any order, and so can addition (as in, 2 * 3 is the same as 3 *2, and 3 + 2 is the same as 2 + 3). So we can rearrange to get back to our original formula if we want:

sin(x + y) = sin(x) * cos(y) + cos(x) * sin(y)

If you made it through that congrats! Now lets actually just implement rotation

After that long explanation, lets look at our actual vector rotation formula again:

newXPosition = vector.x * cos(angleToRotate) - vector.y * sin(angleToRotate) 
newYPosition = vector.y * cos(angleToRotate) + vector.x * sin(angleToRotate)

Remember this is the formula that accounts for the fact that we already know the x and y positions of the current vector. Now lets just implement that in our code:

See the Pen
Vector2 with Rotation
by Rob Louie (@rlouie)
on CodePen.

Alright, we’ve done the hardest math we’ll have to do in a while. On to matrix math next (it’s much easier than this).

Summary

  • Existing vectors already have an angle
  • To rotate a vector you have to add to it’s original angle
  • While not intuitive, computer graphics programs use the angular addition identity for sine and the angular addition identity for cosine to rotate vectors
  • Understanding why the formulas work takes some pretty serious geometry, but the end result is fairly simple

Further Study

Sine Angular Addition Identity

Cosine Angular Addition Identity

Angles of Transversals and Parallel Lines

Arctangent (not super relevant to what we’ve done so far, but used in the initial example)