Vector Tutorial

Alex DienerMar 28, 2010

This tutorial will teach you the basics of vector math. Vectors are useful in 2D and 3D graphics, collision detection, physics, and many other areas of game programming.

What's a vector?

A vector is a set of two or more numbers, often used to indicate a spatial position or a direction. A two-component vector can be used to for two-dimensional space, a three-component vector can be used for three-dimensional space, and so on. Some parts of this tutorial only apply to two-dimensional vectors, while some parts only apply to three-dimensional vectors, as specified in each section. The following struct will be used throughout this tutorial to represent a vector. In the sections that focus on two-dimensional space, the z component will be assumed to be zero.
1
2
3
4
5
6
struct Vector {
  float x;
  float y;
  float z;
};
typedef struct Vector Vector;

Expressing positions with vectors

When a vector is used to represent a spatial position, each component stores an offset in homogenous units, one for each axis. The units stored could be pixels, inches, miles, or anything else; it depends on the coordinate system you use in your game. The origin in a coordinate system is the position represented by the vector {0, 0, 0}; world coordinates are measured relative to the origin. You will sometimes also work in local coordinates, which are measured relative to a position that could be different from the origin.

In the coordinate system used throughout this tutorial, the X axis goes from left to right, the Y axis goes from bottom to top, and the Z axis goes from far to near. This corresponds to the coordinate system most commonly used for three-dimensional OpenGL projections.

Expressing directions with vectors

A vector can also be used to represent a direction. A direction vector is no different from a vector used to represent a position - the only difference is in how you use it. If you draw a line between the origin and the position represented by a vector, that's the direction the vector represents. The magnitude of a vector, also known as its length, is its distance from the origin. Vectors of equal magnitude can be visualized as points on the outside of a sphere (3D) or a circle (2D) with a radius equal to the vector's magnitude. You can normalize a vector, making its magnitude equal to 1. This is useful for a number of things. Example: Let's say you're writing a game in which the player flies a spaceship. You want to be able to move the spaceship not only left, right, up, and down, but also diagonally. When you're moving right, the spaceship goes one unit along the X axis; when moving up, it goes one unit along the Y axis. But what if you're moving both right and up at the same time? To move diagonally, you can't simply move one unit to the right and one unit up, since that's a further distance (approximately 1.4 times as far) than moving one unit on only one axis. And what if you want to move the spaceship at a steeper or shallower angle than 45 degrees?

This problem can be easily solved by using a normalized direction vector. The vector points in the direction the spaceship is facing; when you move the spaceship, you use the vector to calculate the change in position. For example:

1
2
3
4
5
#define SPACESHIP_MOVE_SPEED 0.2f /* Arbitrary value; distance the spaceship moves per frame */

spaceship.position.x += (SPACESHIP_MOVE_SPEED * spaceship.direction.x);
spaceship.position.y += (SPACESHIP_MOVE_SPEED * spaceship.direction.y);
spaceship.position.z += (SPACESHIP_MOVE_SPEED * spaceship.direction.z);

Using a normalized vector, the spaceship moves the same distance regardless of which way it's facing. This certainly isn't the only way; you could accomplish the same thing by storing the ship's direction as an angle, and using the trigonometric functions sin() and cos(). Vectors are just a convenience in this case. More on this later.

Constructing a vector

There are many different ways to construct a vector. The simplest way is to assign each component directly. The vector {1, 0, 0} points straight to the right; {-1, 0, 0} points straight to the left; {0, 1, 0} points straight up, etc. For values that don't point directly up or down one axis, you may want to compute them using the cos() and sin() functions. These functions, given an angle in radians, will return numbers you can use to construct a normalized direction vector. This only works for two out of three axes, though; the third must be set to zero.
1
2
3
4
5
float angle = (M_PI / 4.0); /* 45 degrees */

vector.x = cos(angle);
vector.y = sin(angle);
vector.z = 0.0;

If you want to construct a vector in local coordinates (from point A, how far and in what direction is point B?), you can do it by subtracting each component in point B from the corresponding component in point A. (If you only want the direction, and not the distance, you'll need to normalize the vector afterward):

1
2
3
vector.x = (pointB.x - pointA.x);
vector.y = (pointB.y - pointA.y);
vector.z = (pointB.z - pointA.z);

Normalizing a vector

A normalized vector has a magnitude of 1. Normalized vectors pointing in any direction are of equal distance from the origin, as though constrained to an imaginary sphere or circle. Vector normalization is done like this:
1
2
3
4
5
6
7
8
9
10
void Vector_normalize(Vector * vector) {
  float magnitude;
  
  magnitude = sqrt((vector->x * vector->x) +
                   (vector->y * vector->y) +
                   (vector->z * vector->z));
  vector->x /= magnitude;
  vector->y /= magnitude;
  vector->z /= magnitude;
}

What are we doing here? First, we use the distance formula to calculate the length, or magnitude, of the vector. For a vector that's already normalized, this will be approximately equal to 1. (I say "approximately" because floating point numbers have limited precision, and rounding errors can cause things not to add up exactly to the number you would expect.) Once we have the magnitude, we divide each component of the vector by it.

Note that if you attempt to normalize a vector with a magnitude of zero, you'll end up with a vector full of NaNs. (NaN stands for "Not a Number"; it's a special value returned from illegal operations such as a divide by zero. Languages other than C may behave differently.)

Rotating a vector

Rotating a vector can be accomplished a few different ways. For two-dimensional rotation, one way to do it is by using atan2() to compute the angle of the vector, and using sin() and cos() to compute a new angle. For three-dimensional rotation, you'll need to multiply the vector by a quaternion or a rotation matrix; see the Quaternion tutorial and the Matrix tutorial for more details. For two-dimensional rotation, you might do something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define SHIP_ROTATION_SPEED (M_PI / 100.0)

void rotateShip(Spaceship * ship, float rotationAngle) {
  float angle;
  
  angle = atan2(ship->direction.y, ship->direction.x);
  
  angle += rotationAngle;
  
  ship->direction.x = cos(angle);
  ship->direction.y = sin(angle);
}

void leftArrowKey(Spaceship * ship) {
  rotateShip(ship, -SHIP_ROTATION_SPEED);
}

void rightArrowKey(Spaceship * ship) {
  rotateShip(ship, SHIP_ROTATION_SPEED);
}
First off, we define a constant, SHIP_ROTATION_SPEED, which is an arbitrary value of radians; the higher the number, the more quickly the ship will rotate. When the left arrow key is pressed, it calls rotateShip with a negative rotationAngle, which rotates the ship counter-clockwise. When the right arrow key is pressed, it calls rotateShip with a positive rotationAngle, which rotates the ship clockwise. The first thing rotateShip() does is turn its direction vector into an angle using the atan2() function. The atan2() function can be used to do the inverse of sin() and cos(); that is, it takes coordinates as arguments and returns an angle in radians.

Next, we add in the rotationAngle that was passed to the function. A new direction vector is then computed from the angle using sin() and cos(). The ship is now facing a new direction.

Dot product

Dot product is a vector operation that can be used to compute the angle between two vectors:

1
2
3
4
5
float Vector_dot(Vector vector1, Vector vector2) {
  return ((vector1.x * vector2.x) +
          (vector1.y * vector2.y) +
          (vector1.z * vector2.z));
}
The dot product has some interesting properties. If you take the dot product of two normalized vectors, you can get the angle between the vectors using acos(dotProduct). (The acos() function does the inverse of the cos() function; it takes an X coordinate and returns an angle.) Since the dot product is the cosine of the angle between two normalized vectors, you can also use it to determine how similar they are to each other. If the vectors are at a right angle to each other, Vector_dot will return 0. If the angle between them is less than 90°, the dot product will be positive. If the angle is greater than 90°, it will be negative. If the vectors are the same, the dot product is equal to the vectors' magnitude squared.

Cross product

Cross product is a vector operation that can be used to compute a vector perpendicular to the plane on which two vectors lie:
1
2
3
4
5
6
7
8
9
Vector Vector_cross(Vector vector1, Vector vector2) {
  Vector result;
  
  result.x = ((vector1.y * vector2.z) - (vector1.z * vector2.y));
  result.y = ((vector1.z * vector2.x) - (vector1.x * vector2.z));
  result.z = ((vector1.x * vector2.y) - (vector1.y * vector2.x));
  
  return result;
}
Vector_cross returns a vector perpendicular to the two vectors you pass it. It's best not to worry too much about how the math actually works; as long as you understand how to use it, you can treat the cross product function as a black box.There are a few catches to using cross product. First, the result will not be of the same magnitude as vector1 and vector2 unless they are at an exact right angle to each other, so you may need to normalize the resulting vector. Second, if you pass two parallel vectors to Vector_cross, the resulting vector will have a magnitude of 0. Third, Vector_cross is not commutative - that is, the order of the vectors you pass to it makes a difference in the result. For example:
  • Vector_cross(right, up) = front
  • Vector_cross(front, right) = up
  • Vector_cross(up, front) = right
  • Vector_cross(up, right) = back
  • Vector_cross(right, front) = down
  • Vector_cross(front, up) = left

That's it! You should now be well on your way to using vectors effectively. If you have any questions, feel free to contact me directly, or ask on the message board. Happy coding!

Code Implemenation

Vector.h : Download (1.5 KB)

Vector.c : Download (2.4 KB)

uDevGames 2011

Convergence — Best Gameplay
Kung Fu Killforce — Best Overall Game, Best Audio, Best Presentation
Flying Sweeden — Best Graphics, Most Original
Time Goat — Best Story