Vector Math Basics
To follow up on matrix math basics from last week, I now present some vector math basics. This is fairly basic stuff, so if you’re already an expert at vector math, you can probably skip reading this, but if you’re rusty or have never delved into it, I think it should be pretty useful information.
Vector math is so cool, it sometimes makes me weep openly just thinking about it. (That was a hyperbole…really…) But vector math is cool; it’s easy and does neat things. Vectors are very useful in game programming, including but not limited to: graphics, AI, movement, collision detection and physics.
Definition: A vector is just a direction. An x,y,z coordinate, with the vector pointing from the origin. It does not represent a position!
To get a vector, subtract two points from each other. If you have points A and B, then the vector AB is B-A. The vector BA is A-B.
//Set up the values of A and B here.
//Now that we’ve set ’em up, compute AB
AB = B-A;
Vector Magnitude and Normalization
Remember the Pathagorean theorem? The magnitude (or length) of the vector is defined as:
|AB| = sqrt (AB.x*AB.x + AB.y*AB.y + AB.z*AB.z);
A normalized vector is a vector that has a magnitude of one. Normalization of vectors is very important. For example, most basis vectors of a matrix are normalized (unless they’ve been scaled!). Also, when doing lighting equations in graphics, it’s very important to have normalized vectors (also called normals). To get a normalized vector, you divide the vector by its magnitude:
AB’ = AB / |AB|
cVector normalizedAB = AB;
normalizedAB /= normalizedAB.Length();
Or, even better, just call the Normalise function (that’s right, another English spelling! Get used to it!):
cVector normalizedAB = AB;
In 3D space, the sum of all normalized vectors is a sphere of radius one. Normalized vectors are very useful. They give us direction without magnitude, and are perfect arguments for a very, very cool operation between vectors: the dot product.
The Dot Product
The dot product is so useful, I don’t even know where to begin. For 3D vectors, it is defined as such:
A dot B = A.x*B.x + A.y*B.y + C.z*B.z;
It returns a scaler. For normalized vectors, this actually returns the cosine of the angle between the two vectors.
A dot B = cos(theta)
So what does this do for us? Well, you can use this in a lot of ways. Let’s say you have two characters, Bub and Ronnie. And you want to be able to tell if Bub is behind Ronnie or in front of him. All you know is their position and you have an orientation vector for each:
Just looking at this, you can tell immediately that Bub is behind Ronnie. But in order to tell mathematically, and in your game, you can use the dot product:
BubToRonnie = Ronnie.GetPosition() – Bub.GetPosition();
//remember to normalize before using the dot product!
//use to dot product to see if Bub is behind Ronnie!
//we don’t need to normalize Ronnie’s orient vector because orient vectors
//are usually normalized.
FP32 dot = BubToRonnie.Dot (Ronnie.GetOrient());
We now have the dot product, what do we do with it? First, let’s remember our cosines.
cos(0 degrees) = 1
cos(90 degrees) = 0
cos(180 degrees) = -1
if (dot > 0)
- //Bub is behind Ronnie
- //Bub is in front of Ronnie
Now, let’s say we want to know if Ronnie is in Bub’s view cone. We’ll define his view cone as this:
Again, looking at it, we can definitely tell that Bub can see Ronnie. But we need to use the dot product to figure out if Bub can see Ronnie in our code. Let’s say his view cone is 90 degrees total, so he can see 45 degrees to the left and 45 degrees to the right.
cos (45 degrees) = sqrt(2)/2 = .707106781187
float dot = BubToRonnie.dot (Bub.GetOrient());
const float viewCone = .707107f;
if (dot > viewCone)
- //Bub can see Ronnie, hmm… what should we do? Shoot him?
- Bub.ShootRonnieInTheBackWhileHisHeadIsTurned ();
- //Ok, I advocate not using function names like that
- //We can’t see Ronnie, so look for him
Here’s another quick example, but I won’t go into detail, just so that I can keep this doc a little shorter. Remember that if Ronnie is on Bub’s right or left side, the dot product will return the same thing. It only returns positive or negative if he’s in front or back. So how could you tell that Ronnie is on the left or right side of Bub? Well, first, you’d have to find Bub’s right or left vector:
Now that you have Bub’s left vector, you can dot that vector with the BubToRonnie vector. If the result is positive, Ronnie is on the left side, if it’s negative, Ronnie is on the right side.
So, how did I come up with that left vector. Well, I just happened to know that that is an easy way to come up with a perpendicular vector in 2D space. What if we wanted to create a perpendicular vector in 3D space? Well, we’d use the cross product.
The Cross Product
Remember that in 3D space, any two vectors that are not colinear form the basis for a plane. You can use the cross product of those two vectors to create a vector that is perpendicular (or orthogonal, or normal) to that plane. This is how we compute normals for polygons. This is how the cross product is defined:
C = A X B
|C.x =||A.y * B.z - A.z * B.y|
C.y = A.z * B.x – A.x * B.z
C.z = A.x * B.y – A.y – B.x
Of course, with a cVector, you can just call the function:
cVector A, B;
//Set up the values of A and B somewhere in here
//Now that we have the values of A and B
Another handy thing about the cross product, is that the length of the vector C is the area of the parallelogram created by the two vectors A and B. So if you have a polygon, and you use two of its edges to create the normal C, then C.Length() will be exactly twice the area of the polygon that created it. That’s handy if you want to find the area of a polygon in 3D space. If you are going to use C for a normal, then remember to normalize it.
Tips and Tricks
The dot product is often used for computing lighting. Knowing that the dot product ranges from 1 to -1, we can base our lighting off of two vectors, the polygon normal, and the vector to the light source. This is a very simplistic lighting model, we can get much more complex than this, but this gives you basic directional lighting:
FP32 dotVal = normal.dot(toSun);
//clamp the dot value between 0 and 1,
//this is because everything < 0 is in shadow
dotVal = fabsf (dotVal);
//now use the dot value to interpolate between the ambient light
//and the light source color
Lighting.r = ambient.r * (1-dotVal) + lightsource.r * (dotVal);
Lighting.g = ambient.g * (1-dotVal) + lightsource.g * (dotVal);
Lighting.b = ambient.b * (1-dotVal) + lightsource.b * (dotVal);
Then you can use lighting to multiply into your regular texture color or vertex color, or whatever.
A Look At Matrix
In the Matrix Math Basics tip, I showed how you could convert a 2D orientation vector easily into a matrix. Well, what if your orientation vector is 3D? It’s not quite as straight forward. But, if you remember that a matrix is just 3 basis vectors with a translational vector, and that the 3 basis vectors are all orthogonal (perpendicular) to each other, coming up with a Look At matrix is fairly simple using cross products.
Given a position and a target, we want to compute a matrix. This is handy for cameras, which typically are constructed with a position and a target point they are aiming at. To get the orientation vector, of course we just subtract the position from the target. Now, that gives us one of our basis vectors automatically, because remember that the Z basis vector is essentially your orientation. However, remember to normalize the z vector first before you put it into your matrix. How do we get the X and Y basis vectors? Well remember that these two vectors need to be orthogonal to the Z vector. So we can use the cross product. But wait, we only have one vector, how do we get the cross product with only one vector? Well usually, when you compute a look at matrix, you also pass in an Up vector into the function. The Up vector does just what it sounds like, it defines the up direction. You can tilt your matrix by changing the Up vector, but usually, it is just defined as (0,1,0). Now, to get your X vector, cross the Up vector with the Z vector. This will give you a vector X which is orthogonal to your Z vector. Now if you cross Z and X, you will get your Y vector. Since Z was normalized already, you only have to normalize your other vectors if your Up vector was not normalized. Crossing two normalized vectors produces another normalized vector. (Remember that the length of the new vector is equal to the size of the parallelogram of the two input vectors. If both vectors are length 1, then the parallelogram will be of length 1).
This is the code in x-platform which creates a Look At matrix:
void LookAt( const cVector &eye, const cVector& focal, const cVector& up, cMatrix &result )
- // calculate the 3 axes
:cVector tmp( focal - eye );
|yaxis.Cross( zaxis, xaxis);|
- result.SetAxisX( cVector(xaxis.GetX(), xaxis.GetY(), xaxis.GetZ(), 0.0f) );
- result.SetAxisY( cVector(yaxis.GetX(), yaxis.GetY(), yaxis.GetZ(), 0.0f) );
- result.SetAxisZ( cVector(zaxis.GetX(), zaxis.GetY(), zaxis.GetZ(), 0.0f) );
- result.SetAxisW( cVector(0.0f, 0.0f, 0.0f, 1.0f) );
Note that they normalize the X basis vector. This is only because they weren’t sure if the Up vector was normalized or not. If you knew the Up vector was normalized, you wouldn’t need to normalize the X vector.