Introduction to vectors
I'm going to talk a little bit about vectors from my own and from game development perspective. It's by no means complete or definitive description of what vectors are or how they can be used.
I understand vectors as a bunch of floating point numbers (eg. decimal numbers like 1.123, 1.0, 100.01, etc.) that are grouped together. That's it. Nothing special or particularly difficult about it.
var myVector = new Vec2(2, 3);
Here I've declared a new vector of type Vec2 and assigned it to a variable named "myVector". The vector contains the values 2 and 3. Vec2 just means that it takes in exactly two floating point numbers.
var myVector = new Vec3(2, 3, 4.5);
And here I've declared a Vec3 vector. As you can see it takes three values instead of two. Nothing special about it. But you can probably already start to see a repeating pattern here. Bigger number -> holds more values. That's it.
Computers like vectors. Computers think vectors are really yummy. CPUs are really good at processing vectors. So, we like to use vectors to do computer stuff. That's really all you need to know about it at this level.
Most of the utility of vectors comes simply from how you decide to use them. I can't stress this enough.
Different kinds of vectors are called by different names depending on what kind of values they hold and what those values are supposed to represent (yet the vectors still look exactly the same as any other vectors). At the end of the day all vectors - no matter what they are called - are just a bunch of floating point numbers stuffed together.
Vectors can be used in computer graphics, they can be used to do all kinds of math, or to copy data from memory really fast, etc.
But we're going to be focusing on vectors being used to represent various aspects of the 2D spaces and any entities therein. In other words let's move and rotate something. We'll stick to 2D in the examples for simplicity. Therefore we'll be using vectors which hold only two values like in the Vec2(2, 3)
example we saw earlier.
What are vectors good for in game dev?
Two dimensional vectors can be used to represent basically anything that has some kind of magnitude and direction. They are really good at that.
One way to think of vectors is to see them as arrows (of varying lengths or "magnitude") that are pointing towards something. The direction of the arrow can represent movement direction, rotation, or basically pretty much anything. The magnitude (or length) of the vector can represent things like force, velocity, etc.
You might already be confused and be thinking something along the lines of "How can something that has only two values (defining a point in space) represent both magnitude and direction at the same time? It makes no sense. Is there a hidden value somewhere?"
The answer is, kind of.
Magnitude is calculated based on the origin point (which is not stored explicitly inside the vector itself) and the coordinates of the vector itself. It's important to note that the vector is a translation or an "offset" from it's origin -> to the point defined inside the vector.
Let's look into a distance to target and some other things to understand our magnitude and direction better.
Pythagoras
In game engines you are most commonly using your direction/position vectors to represent a change from a point of origin. We're saying something like "You are at location 1,2 (your origin) and now you want to transform your position by 1,3". That is not "Move to location 1,3", that is: "Move 1 step on the X-axis horizontally, and 3 steps on the Y-axis vertically from your current location, ie. origin. Therefore you would end up at location 2,5, not at location 1,3. Got that? Great.
But how can we find out what the distance to the target is on a straight line? It's unlikely we're going to want to move like the knight on chess board.
When you move like that the A amount of steps you take on the X-axis and B amount of steps you take on the Y-axis are always creating a right angle of 90°. Finally, if you draw a straight line from A to B (in addition to the straight angle) what you have is a proper right angled triangle! Are you starting to see where this is going?
That's when Pythagoras comes in to play.
Pythagorean Theorem tells us that with every right angled triangle you can calculate the length of the longest line on the triangle with the famous formula A^2 + B^2 = C^2
which in essence means that you can figure out the length of that straight line you drew from A to B (also known as hypotenuse) by using this equation.
Let's do an exercise and assume we have var myVector = new Vec2(3, 4);
as our transformation and our origin is at (0,0). In other words we want to move x:3 and y:4 steps from our current location.
To get a proper vector arrow from A to B instead of the chess-knight like pattern we'll plug-in our numbers to the Pythagorean Theorem:
3^2 + 4^2 = DistanceToTarget^2
Above you see that I plugged in the x:3
and y:4
to the left side of the equation and raised the both values to the power of two like Pythagorean Theorem tells us to do. On the right side of the equation I renamed the C
to DistanceToTarget
to make it clearer what's going on. DistanceToTarget also needs to be raised to the power of two.
Raising to the power of two is also called "squaring a number". When we take the number 3, for example, we create a square shape where each side of the square is 3 units long and thus we have a 3 times 3 square composed of 9 units. Essentially you are multiplying a number by itself as many times as the exponent tells you. In this case we multiply 3 by itself two times, ie. 3 * 3 = 9
and 4 by itself two times, ie. 4 * 4 = 16
which changes our equation to:
9 + 16 = DistanceToTarget^2
Therefore:
25 = DistanceToTarget^2
Now we're very close to the solution, but we're still left with a pesky power of two hanging above our DistanceToTarget. We can't calculate DistanceToTarget away because it's the very number we are trying to figure out. But we do need to get rid of it. How are we going to do that?
The opposite of multiplication is division and in similar fashion the opposite of a power of two happens to be the square root. So if we apply a square root on both sides of our equation we should be able to get rid of that extra power of two.
sqrt(25) = sqrt(DistanceToTarget^2)
Pardon my non-mathematical notation. But you get the point. The square root cancels out the power of two and we get:
5 = DistanceToTarget
Which is our final answer.
As we don't want to be distracted by the numbers when explaining a concept, in this example I purposefully picked numbers which are easy to calculate and which result into integers.
As a reminder the square root of a number gives you as an answer the number which you need to multiply by itself to get that original value. You should be able to easily see why square root and power of two are the polar opposites of each other. Essentially in our case we are taking 25 units and arranging them into a square where each side is equally long, ie. 5 units long. 5 times 5 is 25, therefore the square root of 25 must be 5. The math proof for doing this by hand and not in reverse is complex. Usually you just use a calculator to figure out square or cubic roots (it's the same, but it's a cube!).
The most important thing to understand about square roots is simply to be able to visualize geometrically what is going on with those numbers. When you take a square root of something - it's a number of things arranged into a square. That's all there is to it!
In most game engines you usually have a function to figure out things like "Distance from vector A to vector B" or "Distance to target". But you should still understand what is going on under the hood so you can effectively reason about your code and debug it.
Unit vectors aka normalized vectors
Unit vectors are also called direction vectors and normals. They are vectors which have been "normalized", ie. their magnitude has been reduced to 1 while preserving their direction by dividing all the values inside a vector by the calculated length of the vector.
Imagine that you take a car sized arrow that is pointing towards your home and you shrink it until it's the length of your arm - a much more reasonable size for an arrow. And imagine that you take a tiny matchbox sized arrow and stretch it until it also is the length of your arm. And let's call that arm length the length of one. Now you only resized your arrows, they are still pointing towards your home. Congratulations, you understand the most relevant part of normalization.
So, why normalize?
The short answer is: to turn vectors into comparable scale.
To take a contrived example, imagine that you have a treasure chest full of cents, dimes, quarters, and dollars. You also have a scale and you would very much want to figure out how much money you have by weighing your coins. An arduous process when all of your coins have different value and weight and they are all mixed together! So to make the problem simpler you wave your magic wand of normalization and turn all of your coins into the appropriate and comparable amount of pennies. Now that all of your coins have the same weigh and value it's trivial to figure out by weighing them how many you need to pay for a loaf of bread (in case you were wondering, one freshly minted US penny weighs 2.5 grams).
When we normalize vectors we retain their most important quality - their direction - while making them of a standard magnitude (or size/length/etc). Having the same scale makes it trivially easy to run operations on various different vectors.
Let's normalize something for fun. Let's take the example from before with Vec2(3,4)
:
3^2 + 4^2 = DistanceToTarget^2
9 + 16 = DistanceToTarget^2
sqrt(25) = sqrt(DistanceToTarget^2)
5 = DistanceToTarget
Now that we have our calculated magnitude, ie. the length of our vector arrow, which is 5
, we divide all our vector's values with the magnitude of the vector (5) to get, in effect, a normalized magnitude of 1:
3 / 5 = 0.6
4 / 5 = 0.8
Here's our new normalized vector:
var myNormalizedVector = new Vec2(0.6, 0.8);
Your vector is still pointing to the same direction, but it is of "standard length" or "magnitude" now. Nice.
Again, most game engines can do this for you via their pre-implemented functions. But you should still understand how they work to avoid getting confused with your own code.
Scalars
Scalar, like its name tells us, is a value which can be used to scale vectors. In other words it only has a magnitude. It cannot represent a direction.
Vector multiplication and division by a scalar works by multiplying or dividing each of the vector's values one by one, eg:
var myVector = new Vec2(3, 4);
var multipliedVector = myVector * 2; // (3, 4) * 2 = (6, 8)
var dividedVector = myVector / 2; // (3, 4) / 2 = (1.5, 2)
This allows you to make vectors bigger or smaller, ie. you can scale them without changing their direction.
Dot product
Dot product is a method for comparing the relationship between two vectors by returning a scalar. Dot product is especially useful when used with normalized vectors.
Dot product can be used in game development to answer questions like (for example) "Am I looking directly at you?" or "How far away from you am I looking at?" or "Am I looking in the same direction as you?" or "Can I see you if I look at this direction given a field of view of X?", etc.
The scale of the returned values from dot product for normalized vectors ranges from -1 to +1 which represent different rotation degrees where -1 generally means 180° and +1 means 0°.
Coordinates and other math
When dealing with coordinates dot product is the sum of the products of the corresponding entries of the two sequences of numbers. In other words if you have two vectors (x:3, y:4)
and (x:7, y:24)
you multiply the numbers of each vector which correspond with each other (x's with x's, and y's with y's, etc) and finally you add the results together.
Let's try it with our vectors:
3 * 7 + 4 * 24 = 21 + 96 = 117
.
If we normalize the vectors first into (x:0.6, y:0.8)
and (x:0.28, y:0.96)
that gives us:
0.6 * 0.28 + 0.8 * 0.96 = 0.168 + 0.768 = 0.936
Geometry
When looked at from the perspective of geometry dot product is the product of the magnitudes (ie. length) of the two vectors and the cosine of the angle between them. Or - if you normalize the vectors first - then you can just calculate the cosine of the angle between the two vectors!
(Of course you first need to get the angle somehow. I won't get into it here because it is a bit math intensive).
Without normalizing first the formula is:a · b = |a| * |b| * cos(θ)
And with normalizing the formula is simply:cos(θ)
a · b
signifies the dot product of the vectors a and b.
|a| signifies the magnitude (ie. length) of the vector a.
|b| signifies the magnitude (ie. length) of the vector b.
cos() signifies cosine.
θ (theta) is the angle between the vectors a and b.
Cosine is the ratio between length of the hypotenuse of a triangle and length of the adjacent side of a triangle.
We're not going to be calculating cosine by hand because it's generally something you want to use a calculator or math functions of a programming language to solve - unless you're really into math, in which case you can get an approximation by hand, but it's non-trivial to calculate for most people.
Again, game engines come with functions to solve the dot product.
Math and vectors
Unlike with regular numbers math with vectors is all about conventions. In other words basically a bunch of people agreed to a set of arbitrary rules defining how math with vectors works and that's what we're using.
- Two vectors can be added.
- Two vectors can be subtracted.
- A single vector can be multiplied and divided by a scalar.
- Two vectors can be "multiplied", aka cross product (which we won't deal with here).
- Vectors cannot be divided by vectors.
Adding and subtracting vectors
When you add two vectors (x:1, y:2)
with (x:4, y:5)
what you do is you add all of their matching components together. X goes with X, Y goes with Y, and so on.
var a = new Vec2(1, 2);
var b = new Vec2(4, 5);
var result = a + b; // (1, 2) + (4, 5) = (5, 7)
Order of addition does not matter. The same result is gained both ways, ie. by adding a to b and adding b to a.
Adding a velocity vector to a position vector will result in movement to a new position. Simple as that.
var velocity = new Vec2(5, 2);
var newPosition = currentPosition + velocity;
Subtraction works in the same way but in reverse.
var a = new Vec2(1, 2);
var b = new Vec2(4, 5);
var result = a - b; // (1, 2) - (4, 5) = (-3, -3)
To get a direction towards target vector B from vector A you can subtract B - A = DirectionTo
. To understand why this works it's best to try it our yourself a few times on a grid paper.
Scalar multiplication and division
We already covered scalar multiplication above briefly. As a refresher multiplying a vector by a scalar changes the scale, not direction:
var myVector = new Vec2(3, 4);
var multipliedVector = myVector * 2; // (3, 4) * 2 = (6, 8)
var dividedVector = myVector / 2; // (3, 4) / 2 = (1.5, 2)
However, multiplying a vector by a negative scalar changes not only the scale but also reverses the direction of the vector.
Dividing a vector by a scalar only affects the scale.