Introduction to C++ with Game Development: Part 15, Mathematics

Introduction

If you are like me, you probably hate mathematics. For some strange reason, game development is full of it, yet many game developers consider themselves noobs in this area. It might have something to do with the fact that you get away with plenty of trial and error: once your formulas work, you can basically stop trying to understand them.

In this tutorial, we'll have a look at some cool applications of maths, in pretty common situations. So, let's brush up those skills...Follow me.

Clipping

The first practical problem we will be tackling is clipping. Many games will offer you a huge world, but lets face it: it still needs to fit on the screen. If something is completely outside screen boundaries, we simply don't draw it, but what if it's only partially on-screen? Try this:

void Game::Tick( float a_DT )
{
    m_Screen->Line( 200, 200, 800, 300, 0xffffff );
}

The output of this code is... nothing. The reason is simple. Have a look at surface.cpp, line 135 and onward: every line that has its first or last point outside the screen will be skipped completely. Why? Because you can't draw pixels outside the screen. Of course, that's a pathetic solution. It would be much better to clip the line, so that it does fit on the screen. But how was that done again...

clipping lines

Let's look at the red line first. Let's say our screen is 640x480, and the red line starts at (-20, 280) and ends at (380, 440). Figuring out where it enters the screen works like this:

  • While drawing the line, we move 400 (= x2 - x1) pixels to the right.
  • While drawing the line, we move 160 (= y2 - y1) pixels to the bottom of the screen.
  • So, when we travel one pixel to the right, we move 160 / 400 down.
  • So, when we travel twenty pixels to the right, we move 20 * (160 / 400) down.

And there's our answer.

For clipping, you will generally handle the four sides of the screen one by one. For the left side, we can now find a generic answer: if a line starts with a negative x, then it can be clipped like this:

     y1 += -x1 * ((y2 - y1) / (x2 - x1));
    x1 = 0;

If the left side of the red line wasn't the start, but the end, then something similar happens, but you need to swap all variable names. In practice, it's easier to make sure beforehand that the line always goes from left to right. If it doesn't, you just flip it.

So, how would we handle the green line? The maths is similar: this time we want to know how large the bit is that sticks out at the right side. Once we know that, we can adjust (x2, y2). Like this:

     y2 -= (x2 - 640) * ((y2 - y1) / (x2 - x1));
    x2 = 640;

There you go. You should now have the tools to finally fix that line function in the surface class so that it actually behaves like it should, and you can store it for later usage.

Aiming a turret

The second problem also requires some math. Let's first get some code in place. In game.h, find the line that says 'void MouseMove' and replace the {} at the end by a semi-colon (';'). Then, above the Tick method in game.cpp, add:

int mousex, mousey;
void Game::MouseMove( unsigned int x, unsigned int y )
{
    mousex = x;
    mousey = y;
}

What you have just done is this: you removed the default implementation of the 'MouseMove' method, and you replaced it by a new (more useful) one. Now, change the Tick function to:

void Game::Tick( float a_DT )
{
    m_Screen->Clear( 0 );
    m_Screen->Line( 320, 240, mousex, mousey, 0xff0000 );
}

This gets you a line to the mouse cursor, which you will need later on.

Now, imagine you have a gun turret at the centre of the screen, that points to the mouse. How would you do that?

Sprite* gun = new Sprite( new Surface( "assets/aagun.tga" ), 36 );
{
	m_Screen->Clear( 0 );
	m_Screen->Line( 320, 240, mousex, mousey, 0xff0000 );
	int direction = 0;
	gun->SetFrame( direction );
	gun->Draw( 300, 220, m_Screen );
}

Now there's a gun, but we need to figure out its direction. Well, maths to the rescue again! You may recall that sin, cos and tan are quite useful for filling in gaps if you have some information about a triangle.

triangle

In this image, we can use tan for all kinds of interesting operations. We know that tan(a) = Y/X. For our problem, we want a, so we use atan: a = atan( Y / X ). For a given x and y, this will get us the angle. Let's see what we get:

char t[128];
float X = abs( mousex - 320 );
float Y = abs( mousey - 240 );
if (X != 0)
{
    sprintf( t, "%f", atanf( Y / X ) * 180 / PI );
    m_Screen->Print( t, 0, 0, 0xffffff );
}

Add this code in the Tick function, after the call to Clear. The values that atanf returns look like this:

circle

That's wrong of course, but it's still useful information. Assuming that we want 0 at the top (convenient for our sprite), the top-right quadrant is correct if we adjust it using a = (90 - a). The bottom-right quadrant also needs an adjustment: a = 90 + a. The bottom left quadrant is correct when a = 270 - a, and finally, the top-left quadrant should be a = a + 270. So, one question remains: how do we identify the quadrants? The math for that is simple: if ((x > 0) && (y < 0)) then we are in the top-right quadrant. The others are found in the same way.

You now have enough information for the assignment. Go forth and code!

Assignment

Finish the application with the following modifications:

  1. Add a white square on the screen, starting at position (100, 80) and extending to (560,400).
  2. Clip the red mouse-laser to this box.
  3. Draw the correct frame of the aagun sprite, so that it faces the mouse cursor.

Comments

Commenting will be coming soon. In the meantime, feel free to create a discussion topic on the forums.