Geometry and Trig

This chapter contains useful stuff that isn't too difficult to get started with. You'll find it especially useful if you like writing games, and this chapter contains a working example game.

Angles

You probably know what degrees are. For example, 45 degrees means an angle like this:

Real mathematicians™ don't use degrees. They use radians instead:

The comic comes from here.

Most programming languages also use radians, and if you insist on using degrees, you will need to convert between degrees and radians a lot. Let me explain you what radians are, so you can avoid that mess.

Let's draw a circle with radius 1; that is, the distance between any point on the circle and the center of the circle is 1.

Now let's take a slice of this circle so that the arc length of the slice is also 1. The angle we get in the center is then 1 radian.

In general, if you pick a circle slice with arc length $x$, the angle will be $x$ radians:

Here are more pictures of angles that should help you understand this.

As you can see, 6 radians is almost a turn, and 3 radians is almost a half turn. A full turn happens to be $2\pi$ radians, where $\pi = 3.14159...$ is the famous constant, available as something like math.pi or Math.PI in most programming languages.

Let's prove that a full turn is $2\pi$ radians. First of all, if we want to do anything at all with $\pi$, we need to know what it is. Usually $\pi$ is presented like this in schoolbook math:

So, for any circle, if we measure the $d$, the length of the blue part will be $\pi$ times the $d$. It doesn't matter what size the circle is, and I'll explain more about that soon.

Anyway, now let's have a look at the picture with an angle of $x$ radians, and let's modify it so that it represents the angle of a full turn:

The length of the long, black line is $2$ because it's double the length of the $1$ line. Comparing the pictures gives $d=2$ and $x=\pi d = \pi \cdot 2 = 2\pi$.

Exercise

Find a round object and measuring tape, and use them to calculate $\pi$. You should get quite close to 3.14. Like I said, any round object will do, and you will always get the same value.

The animation comes from here.

Here are pictures of important angles in radians. Spend a while looking at this section so that if I say $\pi / 2$, you'll immediately think of an angle like ∟.

Another Exercise

How many degrees is $\pi$ radians? How about 1 radian? How about $x$ radians? Write a radians_to_degrees() function using your favorite programming language.

Scaling

If we multiply all measurements of a shape or picture by a positive number, the size changes. For example, here the blue triangle is the yellow triangle scaled up by 2:

Note that angles do not change when scaling. These pictures represent the bottom-left corners of the above triangles, and as you can see, they are identical:

Scaling with a number less than 1 is known as scaling down, and it makes things smaller. For example, dividing by 2 is same as multiplying by $\frac 1 2 = 0.5$, so dividing everything by 2 does this:


Scaling seems to change the lengths of all straight lines the same way, but it works for curvy things as well. We can explain this by thinking of the curve as if it consisted of a bunch of short and straight lines. It doesn't, but if we use many lines, it will be "accurate enough"; more about this soon. Also, note that I said "explain", not "prove"; the purpose of this thing is to give you an idea of why the curve scaling thing works, and I have no idea whether real mathematicians think that this explanation is a perfect proof.

For example, let's say we have this arc:

Let $N \in \{1,2,3,...\}$ be the number of short lines, and let $L_1,L_2,L_3,...,L_N$ be the lengths of the lines. Like this ($N=8$ in the picture, but it doesn't have to be 8):

Total length before the scaling is then $L_1+L_2+...+L_N$. Let $s > 0$. After scaling by $s$, the lengths are $sL_1,sL_2,...,sL_N$, and the total length is then $sL_1+sL_2+...+sL_N$. We can factor it:

$$\begin{align}\underbrace{sL_1+sL_2+...+sL_N}_\text{after scaling} = s \cdot \underbrace{(L_1+L_2+...+sL_N)}_\text{before scaling}\end{align}$$

The total length got scaled by $s$.

Note that this was just an approximation. Mathematical curves don't need to consist of straight lines, but even though it is an approximation, we can make any errors as small as we want by choosing many lines and making them smaller. Everything we did above will work the same, and it doesn't matter how many things the $...$ parts contain. The $N$ can be as big as we want; if we set $N=1000000000000000000000$ or something, everything will be really precise.

For example, this drawing has 20 lines instead of 8, and it's already much harder to actually see the lines:


Now we are ready to understand why $\pi$ appears in all circles. Let's draw a circle with diameter $d$, and let $x$ represent the length of the blue line. (Yes, we'll always have $x=\pi d$, but we are trying to prove it now, so we must pretend that we don't know it yet.)

Now dividing the lengths gives $\frac{\color{blue}x}{d}$. You'll see why we divided them in a moment.

Let $s \in \mathbb{R}, s > 0$. Scaling by $s$ gives this, because all measurements get multiplied by $s$, including the $x$:

In my pictures, $s > 1$ because scaling by $s$ made the thing bigger, but it works the same way if $0 < s < 1$ or $s=1$. Let's do the same division thing again based on this picture:

$$\begin{align}\frac{\color{blue}{sx}}{sd} = \frac x d\end{align}$$

The $s$ cancels so that we end up with the same $\frac x d$ ratio every time, no matter how we scale the circle. In other words, this works for circles of all sizes. The $\frac x d$ ratio is called $\pi$, and we get our final result:

$$\begin{align}\frac x d &= \pi \\ x &= \pi d\end{align}$$

Coordinates

Here's a boring number line that represents $\mathbb{R}$.

If we have two number lines called $x$ and $y$, we can do this:

Every point can now be represented with two numbers. These are known as the $x$ and $y$ coordinates. For example, the $x$ coordinate of the red point in the below picture is $3$ and the $y$ coordinate is $-2$:

Usually $x$ goes before $y$ for historical reasons, so $(3,-2)$ means $(x,y)=(3,-2)$.

So far this probably feels like some useless gibberish that has nothing to do with real life, but most programming languages have something that works with coordinates. Usually these coordinates work so that 1 unit represents 1 pixel on the screen, and the $y$ axis is upside down compared to what mathematicians are used to. Like this:

Basic Coordinate Stuff

There is a working ball and paddle at the end of this chapter. In that game, I have code like this:

ball.centery = screen.height - paddle.thickness - ball.radius;

In this game, all coordinates are in pixels. When you see something like this, you can always draw a picture and figure out what it does. For example, let's figure out where the ball ends up when this code runs. In the game, there is no space between the bottom of the screen and the paddle, so the picture looks like this:

There are many things in the picture, but if you look at it, you can now see what the code is doing. Let's go through it a step at a time to make sure you understand all of it:

If you are still following, that's great! This example is probably the most complicated calculation in my game, and now you should have no trouble at all with understanding the other things.

Exercise

The $(x,y)$ location of something usually means the top left corner in programming. Where does the bottom right corner of the window go in this GUI code?

window.resize(300, 200);    // (width, height)
window.move_to(600, 500);   // (x, y)

Take a piece of paper and draw a picture of the whole screen with the window in it; it'll help you to understand how this works.

Trig with the Unit Circle

This section uses the Greek theta letter $\theta$ because mathematicians often use it to denote an angle. Don't be confused, it's just a letter.

Here's a problem that combines the coordinate stuff with the angle stuff above. Let $\theta \in \mathbb{R}$. The unit circle means a circle with radius 1. Let's draw a unit circle in the center of the xy plane, and let's draw the $\theta$ angle to it.

There are quite a few things going on in the above drawing. Make sure you understand them before moving on.

Now, what are the coordinates of the red dot? Mathematicians have defined a couple functions just for solving this problem:

$$\begin{align}x &= \cos(\theta) \\ y &= \sin(\theta)\end{align}$$

The function names are $\cos$ and $\sin$ for historical reasons. Almost all programming languages have cos and sin functions, and you don't need to know how they are implemented. You just need to know what they do.

As before, here's a pile of examples:

You can check these with Python:

>>> import math
>>> math.sin(math.pi/2)
1.0
>>> math.cos(math.pi/2)
6.123233995736766e-17

Mathematically $\cos(\frac \pi 2) = 0$, but Python's math.sin and math.cos use floats. Floats are not mathematically precise. For example, 0.1 + 0.2 is not exactly 0.3, but something very close to it:

>>> 0.1 + 0.2
0.30000000000000004

math.cos(math.pi/2) returned 6.123233995736766e-17, and the e-17 at the end of the number means that we actually have 6.12323399... with the . moved left by 17 places (and zeros added as needed):

$$\begin{align}0.00000000000000006123233995736766\end{align}$$

As you can see, this is just a tiny error, and the result is in fact very close to $0$.

Exercise

The above drawings have $\theta \ge 0$, but I said $\theta \in \mathbb{R}$, so it can also be negative. What is $\sin(-\frac \pi 2)$? Try to first solve it by drawing a picture, and check the answer with your favorite programming language.

Now it's time to generalize this to circles with a radius other than 1. Let $r \in \mathbb{R}$ and $r > 0$. Let's scale this thing...

...by $r$:

Like I said earlier, angles don't change when scaling, but lengths do. Here $\theta$ represented an angle and an arc length, so we have the old $\theta$ angle and a scaled $r\theta$ arc length.

The coordinates also get scaled by $r$ just like other lengths, because you can think of them as lengths along the axises:

Exercise

The player of a 2D game should move by 2 pixels every time a player.move() method is called. It means that the player goes to some place on the circle with radius 2 around it:

Note how $\theta$ is clockwise here but it has been counter-clockwise until now; clockwise angles are a programming thing while mathematicians do things counter-clockwise. This works nicely with the "upside down" $y$ axis.

Let player.x and player.y be the current coordinates of the player as floats (they may need to be rounded to ints when actually drawing things). Let player.speed = 2 and player.angle = θ. Write the player.move() method using your favorite programming language, and compare it to the moving code in my ball and paddle game.

Example: Ball and Paddle

Here's a minimal game written with my canvaswrapper.js script that demonstrates most things we have learned so far. Click it, press arrow up to start the game and then use arrow keys to move the paddle.

Here's the code:

var screen = new CanvasWrapper('canvas1');

/* handy things:
    paddle.centerx - paddle.length/2    x coordinate of left edge
    paddle.centerx + paddle.length/2    x coordinate of right edge
    screen.height - paddle.thickness    y coordinate of top edge
    screen.height                       y coordinate of bottom edge
*/
var paddle = {
  // centerx is added later
  length: 200,
  thickness: 15,
  speed: 10,
  movement: 0,    // -1 means left, 1 means right
};

/* handy things:
    ball.centerx - ball.radius      x coordinate of left edge
    ball.centerx + ball.radius      x coordinate of right edge
    ball.centery - ball.radius      y coordinate of top edge
    ball.centery + ball.radius      y coordinate of bottom edge
*/
var ball = {
  // centerx, centery, angle and moving are added later
  radius: 10,
  speed: 10,
};

function resetGame() {
  paddle.centerx = screen.width/2;
  ball.centerx = screen.width/2;
  ball.centery = screen.height - paddle.thickness - ball.radius;
  ball.angle = 1.5*Math.PI;     // up
  ball.moving = false;
}

resetGame();
runRepeatedly(function() {
  screen.fill('black');
  screen.drawRectangle(
    // left x, top y, width, height, color
    paddle.centerx - paddle.length/2, screen.height - paddle.thickness,
    paddle.length, paddle.thickness, '#00ff00');
  screen.drawCircle(ball.centerx, ball.centery, ball.radius, 'white');

  if (ball.moving) {
    if (ball.centery > screen.height) {
      resetGame();
      return;
    }

    ball.centerx += ball.speed * Math.cos(ball.angle);
    ball.centery += ball.speed * Math.sin(ball.angle);

    if (ball.centerx - ball.radius < 0) {     // bumps left wall
      ball.angle = Math.PI - ball.angle;
      ball.centerx = ball.radius;
    } else if (ball.centerx + ball.radius > screen.width) {   // right wall
      ball.angle = Math.PI - ball.angle;
      ball.centerx = screen.width - ball.radius;
    } else if (ball.centery - ball.radius < 0) {          // top
      ball.angle = -ball.angle;
      ball.centery = ball.radius;
    } else if (ball.centery + ball.radius > screen.height - paddle.thickness) {
      // paddle?
      paddleLeft = paddle.centerx - paddle.length/2;
      paddleRight = paddle.centerx + paddle.length/2;
      if (paddleLeft < ball.centerx && ball.centerx < paddleRight) {
        // yes, it hits the paddle
        ball.angle = -ball.angle;
        ball.centery = screen.height-paddle.thickness-ball.radius;

        // also adjust the angle depending on which side of the paddle the
        // ball hits, 100 is a constant that I found with trial and error,
        // and it seems to work well
        ball.angle += (ball.centerx - paddle.centerx) / 100;
      }
    }
    paddle.centerx += paddle.movement*paddle.speed;
  }

  screen.getEvents().forEach(evt => {
    if (evt.type == 'keydown') {
      if (ball.moving) {
        if (evt.key == 'ArrowLeft') {
          paddle.movement = -1;
        } else if (evt.key == 'ArrowRight') {
          paddle.movement = 1;
        }
      } else {
        if (evt.key == 'ArrowUp') {
          ball.moving = true;
          return;
        }
      }
    } else if (evt.type == 'keyup' && (
          (evt.key == 'ArrowLeft' && paddle.movement == -1) ||
          (evt.key == 'ArrowRight' && paddle.movement == 1))) {
      // cancel the previous ArrowLeft or ArrowRight press
      paddle.movement = 0;
    }
  });
});

Exercise

A part of my game works similarly to this pseudocode:

if (the ball hits the left wall or the right wall) {
    ball.angle = Math.PI - ball.angle;
}
if (the ball hits the top wall or the paddle) {
    ball.angle = -ball.angle;
}

Figure out why this makes the ball bounce correctly by drawing pictures of the ball hitting the walls.