Scalars or numbers
The real numbers form an algebra. We can add, subtract, multiply and divide them. The addition and multiplication is left and right distributive
Obviously the multiplication is compatible with scalars.
Multiplication is also commutative.
Numbers have their negation which gives zero when added, as well as their reciprocal which gives 1 when multiplied.
Powers
Multiplication is a shorthand for addition
Likewise powers are a shorthand for multiplication
Numbers to a negative power are the reciprocal of the same number to the positive power.
Fractional powers gives use operations like square root
Vectors
Vectors take on many forms in games. We can find them for example as points, translations, forces or even rotations. A vector is something which can’t be expressed using one number. For example a point in 2D needs 2 numbers, an x and an y value.
Addition
There are different ways to see a vector. We can use a 2D vector to identify a point in 2D space. For example the point (3, 2) can be seen as the absolute coordinates of a specific point. But we can also see it a translation of the origin 3 units in the horizontal direction, and 2 units in the vertical direction. We can apply this translation to any point by using vector addition.
Even though all three terms are vectors, we can see this expression as point + vector = point.
Multiplication with a scalar
We can’t multiply two vectors. But if we multiply a vector by a scalar, we get a new, scaled vector.
When we scale, only the distance traveled changes, not the direction. This is because the scale is uniform, both dimensions are scaled equally.
Subtraction
Since we can multiply with the scalar -1, and in that way obtain the opposite vector, we can also subtract vectors.
This allows us to make the translation vector t which translates a point u to another point v.
So in this case we see the expression as vector = point - point. Note that to make a vector from two points, you subtract the origin from the destination. The order is important.
Division by a scalar
Since scalars have a reciprocal, we can divide by a scalar as well.
Unit vector
If we scale a vector by the reciprocal of its length, we obtain a unit vector. A unit vector always has length 1.
In situations where only the direction of the vector matters, it is often important to use a unit vector.
Polar coordinates
If we look at a vector as a translation, we can see that it moves a point along a certain distance and in a certain direction.
Until now, we saw a vector as a Cartesian coordinate . But if we look at it as a length r and an angle we can see it as a Polar coordinate . It is still a vector, but the meaning of the elements is different. It is fairly easy to convert between the two. From Cartesian coordinates we can find the corresponding Polar coordinates as
Why the length of the vector is the square root of the sum of the squares of its elements will be discovered shortly. We get the angle by turning the slope into an angle using atan2. The atan function takes just a slope and returns the corresponding angle as between (-90) and (90). The atan2 function is similar, but looks at the signs of both x an y to determine the correct quadrant and returns an angle between 0 and (360).
From Polar coordinates we can find the corresponding Cartesian coordinates as
Remember, cosine and sine are the x and y coordinate respectively of the unit vector pointing in the direction of the given angle. If we scale the cosine and sine by the length of the vector, we obtain the elements of the vector itself.
Now we might think that we are able to rotate a vector, by converting it to Polar coordinates, changing the angle and converting it back to Cartesian coordinates. And indeed, we could do that, but it would be slow. This is because trigonometric functions come with some cost. Why this is, is explained in the chapter on calculus.
To find a better way to rotate a vector we need to look at complex numbers.
Complex numbers
Complex numbers in math classes are mostly seen as a way to take the square root of a negative number. This is possible because , thus for example . The letter stands for the imaginary number. For game developers complex numbers are much more valuable than that though.
A complex number is usually written as .
We can visualize it just like a vector, with and .
Addition and subtraction
Like numbers, and vectors, complex numbers can be added and subtracted.
Multiplication
However, unlike vectors, complex numbers can also be multiplied.
If we look at the angle of the direction of this product, its angle is the sum of the angles of and . Thus multiplying complex numbers adds their angles. This is what we can use to rotate vectors. If we want to rotate a vector (x, y) by an angle , we only need to convert this angle to a vector, which we saw is , and use complex multiplication to obtain the rotated vector.
Note that it is important that we multiply with a unit vector, if not, we would introduce a scale.
You might recognize this rotation formula, possibly in its following matrix form.
This result is quite important as it shows that we are not really rotating using an angle, we are rotating using the unit vector . It is important to realize this as we often do unnecesary conversions. For example when we want to make a sprite point towards the mouse, we often create a vector
Then we take the angle from this vector
And rotate the sprite using this angle. However, to actually rotate, the angle is converted to cos(angle) and sin(angle). This is just the unit vector of dir.
We could as well have taken the unit vector of dir and rotated using this vector. Going through angles is a roundabout way. Blame bad math classes for the fact that most graphics APIs only have rotate(angle: number) and not rotate(direction: vec2).
Division
Complex numbers have a reciprocal
This means we can not only multiply complex numbers, but divide as well
When we examine the angles again, we see that complex division subtracts angles. If we have two vectors (a, b) and (c, d), complex division can give us a vector which has an angle equal to the angle of (a, b) minus the angle of (c, d). This gives us a formula to calculate the angle between two vectors, without having to calculate their individual angles. We don’t need to go through Polar coordinates.
Remember that we could get the angle of a vector using atan2.
We can fill in the result of the quotient of our vectors. (observant people will have noticed that we switched the sign of the imaginary part. This is because in our 2D game engine the y axis goes down instead of up as is usual in mathematics).
But wait, atan2 just needs the slope y/x, and the individual signs of x and y. So the denominator isn’t needed. It will get divided away anyway. So to get the angle between two vectors we just need to use
These two formulas for y and x do look familiar don’t they? this is because they are the 2D dot and cross products.
Vector dot and cross products
If we have these functions, derived from complex division we can write the angle between two vectors as
Note that the order of the operants is important, as it is for the cross product. Thus
We should by now realize that in the case of unit vectors, dot gives us the cosine of the angle between these vectors. While cross gives us the sine of the angle between these vectors. In case the vectors are not unit, we will have a multiplier equal to the product of the lengths of the vectors.
This is why dot(u, u) gives us the squared length of u. The angle between u and u is obviously zero, thus the cosine is 1.
Since
We get the formula for length
2D Vectors do not form an algebra
Since 2D vectors do not have a product which is left and right distributive, they do not form an algebra. The complex numbers do, as they do have a product which is not only bilinear (left and right distributive) but even both commutative and associative. So the complex numbers form an algebra. 3D vectors have a cross product which is left and right distributive and they form an algebra. Their cross product is neither associative nor commutative though. If we look at quaternions, the equivalent of complex numbers used in 3D, their Hamilton product is bilinear and associative.
Polynomials
Polynomials are expressions containing a variable and using only operators +=*/ and exponentiation with positive integer exponents. For example
Polynomials form an algebra, with polynomial multiplication.
The line and linear interpolation
The most simple polynomial, one of degree 1, is the line. A line is typically written as . With a and b constants. The constant a is called the intercept (where we cross the y axis when x is 0), and b the slope.
We see that if we choose the slope as (b-a), that we obtain linear interpolation or lerp. What happens is that we create a line which goes from a to b as t goes from 0 to 1.
Since we have addition, subtraction and scalar multiplication for vectors, we can do this for vectors a and b as well. Like this we can move from one location to another with constant speed.
What if the vector encodes a direction to use for rotation though. Say we have two vectors we use to rotate, can we just use linear interpolation? Not without modification. The problem is that a linear combination of two unit vectors will not give a new unit vector. This means we need to normalize the new vector, to make it a unit vector again. This gives normalized lerp.
However, this lerp does not have constant speed. There is a better way. The problem here is that we looked at direction vectors as vectors. But for direction vectors and the angles they represent, we should use complex number operations. As we saw, adding angles is multiplying complex numbers, and subtracting angles is dividing complex numbers. We’re only left with multiply, which becomes exponentiation. Putting it together, we get slerp (spherical linear interpolation, which is actually exponential interpolation).
This is what is used to interpolate between quaternions (the equivalent of complex numbers used in 3D graphics). While it looks promising, there is a caveat unfortunately. The power operation isn’t that easy to do. While we know how to multiply complex numbers, and thus theoretically can calculate where n is an integer, we’re actually using real powers between 0 and 1, not integers. A solution is to use the Euler representation of a complex number.
Where r is the length of the vector and its angle. Knowing this, it is easy to take a power of a complex number using DeMoivre’s Theorem
However, to do this we need to convert the vector to Polar coordinates and back to Cartesian coordinates. Something we didn’t like due to it’s performance impact. So while slerp is quite an elegant solution mathematically, not being able to take a real power of a complex number directly kind of spoils the fun.
Still, slerp is not only for vectors or complex numbers in particular. It can be used on every multiplicative value. Now what is a multiplicative value? If we look at pos or angle, we combine two translations or angles by adding them.
However combining two scale factors is done by multiplying them.
To add scale factors, you multiply them, just like you do with complex numbers. This means that scale actually should use slerp, not lerp. If we look what lerp gives for lerp(2, 8, 0.5), we get 5, while slerp(2, 8, 0.5) gives 4. If you think about scale, a scale halfway between 2 and 8 would be 4, not 5. Another place where we see this is audio. If we look for an audio sampling rate between 11025Hz and 44100Hz, then lerp gives us 27562.5Hz, while slerp gives us correctly 22050Hz.
So do all engines and editors interpolate scale wrong? Yes, yes they do. Just like the lack of a rotate function with a vector parameter, we are dealing with something which isn’t well understood by most people. Some audio engineers know, but graphics people are still in the dark.
Matrices
A matrix can be seen as a vector of vectors (though more dimensions, thus deeper nesting, is possible). The size or shape of a matrix depends on what we are using it for. In 2D games, the place where you might encounter it most frequently is transformations. Now you might ask, “why use a matrix?”, as you can already translate, rotate and scale vectors now. The reason is mostly composition and inverting transformations. It is easy to compose two translations, by just adding them. But what if you add a rotation? How do you store both the rotation and translation. And when you translate after the rotation, your next translation should work according to the new rotated coordinate system.
To make all this easier, we use matrices. Though matrices are not the only way to do this, as we’ll see later, there are other more compact constructs as well.
To add transformations, we multiply their matrices. Just like complex numbers.
Matrix multiplication
Multiplying a matrix A with a matrix B means taking the dot product of the row vectors of A with the column vectors of B.
Note that this multiplication is not commutative, thus
This is good because transformations are not commutative.
Identity matrix
To start with transformation specific matrices, here is an identity matrix. This matrix is like the number 1. If you multiply by 1, nothing happens. It’s the same with this matrix. Multiplying by it gives you just the original matrix.
Translation matrix
Now, our vector is only 2 dimensional, so why a 3 dimensional matrix? It’s to support translation. If we want to translate by a vector t, our translation matrix looks like
So let’s multiply our vector v with that matrix to see what happens
As you can see, we wrote our vector in the form of a row matrix, and we added a 1 to the end. Our result is a row matrix, also with a 1 at the end. As we can see, we successfully translated v by t, but what is that 1. The 1 at the end is actually a handy tool to distinguish points and normals. If our vector was a normal, it would have a 0 at the end. Let’s see what would happen.
Nothing seems to have happened. Which is exactly what we want. If we rotate a shape, normals will also rotate with the shape, but normals shouldn’t translate if the shape is translated. This is because normals show the direction of the surface at the point where they are located, and the neither the surface , nor the normal’s location relative to the surface changes under translation.
Rotation matrix
So now that we have that established, let’s rotate. We already saw how this matrix looks in the section about complex numbers.
Scale matrix
Scale is even easier. If we want to scale by s, we get
Note that if we only want to allow a uniform scale, we need to keep .
Matrix transpose
The transpose of a matrix is one where rows are switched to columns.
For a row vector that means that it becomes a column vector
The transpose of the transpose of a matrix results in the original matrix.
Pre and post-multiplication
What we used for our transformation matrices is pre-multiplication, as the vector went in front of the matrix.
Most math and graphics systems use post multiplication. The matrices are the same, but transposed. Vectors are column vectors in this case.
Combining transformations
As said, the most common usage for matrices in a graphics API is combining transformations. They are combined using matrix multiplication. Since the multiplication is not commutative, the order matters. Most systems use a TRS transformation for nodes, translation, rotation and scale. For a post-multiplication system, it will look like this. This makes sure the points are first scaled, then rotated, finally translated. If we would use the reverse matrix order, the translation direction would be influenced by the rotation and the translation distance by the scale.
If we would use the reverse matrix order, the translation direction would be influenced by the rotation and the translation distance by the scale.
Let’s test this with a translate and scale matrix. As we can see, the scale leaves the translation alone.
Now let’s switch them. We can see that the translation is affected by the scale, and the distance and possibly the direction we translate to has been modified.
Anchor
We didn’t incorporate the anchor yet. Currently the scale and rotation will happen from the local origin, after the translation. To rotate and scale from the anchor, we need to first translate the anchor to the origin, then perform the scale and rotation, and then translate back.
Inverse Matrix
When we have a scalar , the scalar can be defined so that , as long as . Similarly, we can calculate the inverse matrix from so that .
The requirement for a matrix to be invertible is having a determinant which is not 0.
The determinant
The determinant can be calculated in different ways, but what is it really? The determinant of is 1, so is the determinant of a translation or rotation matrix. Only a scale matrix or a transform containing a scale has a determinant different from 1. For a 2 by 2 matrix
it is the surface area of a parallelogram made from (0, 0) (a , b) (c, d) and (a + c, b + d), thus the determinant is . Note that if we write the vectors (a, b) and (c, d) as the previous matrix, their cross product is the determinant of this matrix.
For a 3 by 3 matrix
it is a volume equal to . This determinant can also be written as a triple product consisting of dot and cross products
The determinant is used in various situations, but right now we’ll use it to calculate the inverse matrix. It is logical that if we calculate the inverse of a matrix which scales, that we will need one which divides by that scale to negate it. This is why, if the matrix has a zero scale, we can’t calculate the inverse, since we would divide by zero.
Later we will learn about decomposition as well as eigenvectors and eigenvalues which can give us the actual rotation and scale of the matrix. But for now we’ll continue calculating the inverse.
Calculating the inverse matrix
There are various ways to calculate the inverse. We can use the adjunct matrix, use various decompositions, solve equations, etc. For now, we will just give the solutions for 2 by 2 and 3 by 3 matrices. The inverse of a 2 by 2 matrix A is
The inverse of a 3 by 3 matrix A is
Why do we need the inverse? If we have an object which is transformed in order to place it in the world somewhere in a certain orientation with a certain scale, we have a transform which transforms local points to a point in the world. If we can invert this transform, we can transform world points to local points. If we have a transform from local to screen, we can calculate the inverse in order to calculate the mouse position into a coordinate local to an object. This are just a few examples, but there are really many places where this is useful. Like ellipses for example, these can be described as a matrix, which means we can find the transformation to transform an ellipse to a unit circle by inverting the matrix. This then helps us to simplify finding intersections with various shapes.
Matrix decomposition
Before we look at more formal methods, let’s take a look at our transformation matrix. We generally have the following form
We don’t need to care about the last row. In fact we don’t even need to store it, as it is always the same. The translation is very easy to extract, as it is always in the last column. Rotation and scale need a bit of math since the remaining 2 by 2 matrix is a mix of
and
So
Since we know that , We can get the scale as
Since , we can get the rotation angle as