The act of moving an object in 2D or 3D space is called a transformation. It’s an umbrella term that covers translation, rotation, and scaling. We’ll start off with the math for these operations in 2D, then in the next video, we’ll go over some conventions of 3D coordinate systems before introducing the math for transformations in 3D. In this and the next video, we’ll stick to the methods that utilize just simple algebra and trigonometry, but in a later video, we’ll explain how to perform transformations using matrices, which is a less intuitive approach but more expressive and hence the preferred approach in 3D rendering.
Start with the simplest transformation: translation of a point in 2D. If we want to move the point in a direction for a certain distance, we express the movement as a vector, a coordinate relative from the starting point, and simply add the vector to our starting coord. So here if we start with a point at coord 5,3 and wish to move it right 1 unit and up 4 units, we simply add the vector (1, 4) to (5, 3) yielding (6, 7). Expressed as a formula, x prime, our new x, equals the original x plus x of the vector, and y prime, our new y, equals the original y plus y of the vector. So notice that we could think of the translation as two separate movements along the axes: first right along the X axis, then up along the Y axis. Or we could think of it the other way, first up along the Y axis, then right along the X axis. It actually doesn’t matter because, as this illustrates, translations are commutative, meaning we can perform a sequence of translations in any order and our point ends up in the same place. We might also say that translations are cumulative: given any number of translations to perform in sequence, we could just add them together into one translation.
So translations are by far the simplest transformation: simply add the vector to the point. If you have a an object made up of many vertices, you move the object by simply adding the same vector to every vertex. In 3D, translations aren’t any more complicated: the vectors and points simply have one more dimension, and that dimension gets treated just the same.
Rotations get far trickier. We’ll start with the simple case first of rotating in 2D around the origin. The question is, for some point (x, y) which we rotate by angle theta, what is the formula that gets us the resulting position (x prime, y prime). We can derive this formula by first observing that the distance from the origin to the new point with be the same as the distance from the origin to our starting point. We’ll call the distance r (for radius). We can compute r from our starting point using the pythagorean theorem because x, y and r form a right triangle with r as the hypotenuse. With the distance r, we could figure x prime, y prime if we just had the angle from the X-axis, and we can find this angle by adding theta to the angle from the x-axis to the starting point, which we’ll call phi. But we first need phi itself, which we can figure from the arctangent of y over x. If your memory of trig is hazy, recall that the tangent of an angle equals y over x; the arctangent is the reverse operation which, given y over x, tells us the angle. So anyway, now we have phi, which we can add to theta to get theta prime, the angle from the X-axis to the line of x prime, y prime. Recall from trig that the cosine of an angle equals x over r and the sine of an angle equals y over r. So x prime over r equals cosine theta prime, and y prime over r equals sine theta prime. Solving for x, x prime equals r cosine theta prime, and solving for y, y prime equals r sine theta prime.
Note that our formula holds up when the starting point is in another quadrant. Here the starting point is in the forth quadrant, but even if phi is negative, adding phi to theta still yields the correct angle for x prime, y prime, so the formula works out the same. Even if we treated the angle to x, y as positive instead of negative, e.g. phi equals 340 degrees instead of negative 20 degrees, the trig still works out the same.
Anyway, now we have a formula for rotation in 2D around the origin. This works not just for single points but for objects made up of multiple vertices: if you want to rotate an object around the origin by some number of degrees, simply apply the same formula to each vertex.
Actually, the formula we just derived is not commonly used because there’s a more efficient formula. The efficient formula starts with similar reasoning but takes advantage of the trigonometric identity for the cosine and sine of two angles summed together. According to a proof we won’t go over here, the cosine of two angles A and B added together equals the cosine of A times the cosine of B minus the sine of A times the sine of B. Similarly, the sine of A plus B equals the sine of A times the cosine of B plus the cosine of A times the sine of B. With these two given facts, we can simplify the amount of work we must do to perform a rotation. Starting from where we left off, we know that x prime equals r cosine theta prime and y prime equals r sine theta prime, but let’s break theta prime back into its components, theta plus phi. Well then, by the two given trigonometric identities, we can say that x prime equals r cosine theta cosine phi minus r sine theta sine phi, and y prime equals r sine theta cosine phi + r cosine theta sine phi. Now, by the definition of sine and cosine, r cosine phi equals x and r sine phi equals y. So we can simplify both of these equations, removing all references to r and phi. We end up with x prime equals x cosine theta minus y sine theta and y prime equals x sine theta plus y cosine theta. And that’s our simple formula for rotation which conveniently doesn’t require us to compute either r or phi, making it more efficient.
Seeing it in action, say we have a point (4, 1) which we want to rotate 60 degrees around the origin. Plugging the values into our formula, the new x equals 4 cosine of 60 degrees minus 1 sine of 60 degrees, and the new y equals 4 sine 60 degrees plus 1 cosine of 60 degrees.
Translating the formula into code, we define a function rotatePoint that takes a point, specified as an array of an x and y value, and takes an angle as a value in degrees. In the function, we first unpack point into x and y variables, then convert from degrees to radians by the formula degrees multiplied by pi divided by 180. With the angle in radians, we get the cosine and sine of our angle, which we’ll call c and s for short. Then plugging the values into our formula, the newX equals x times c minus y times s, and the newY equals x times s plus y times c. Finally, we return an array with the rotated coords.
Once we can rotate around the origin, the next question is how to rotate around some other pivot point. The solution is a simple trick: if we first translate our point such that it sits in the same place relative to the origin as to the pivot, we can rotate around the origin as normal if we afterwards undo our first translation. We’re temporarily changing the frame of reference, doing our rotation, then just shifting the frame of reference back. We simply subtract the pivot point coord from our point coord before the rotation, then add the pivot point coord back in after. So in our formula, we subtract x of the pivot from x of our point and y of our pivot from y of our point, but also add x of the pivot to the x prime and y of the pivot to y prime. It’s actually a bit easier to understand in discrete steps of code than as a formula. In our rotate function, here, we’ve added a third parameter which takes a pivot coordinate, another array of an x and y value. We then unpack this coordinate as xp and yp, subtract them from our x and y, but then add them back in to our new x and new y values. With this function, now we can rotate a point around any pivot point.
It’s important to understand that rotations, unlike translations, are not always commutative. Rotations around the same pivot are commutative, but rotations around different pivots are not. Here, for example, we have a starting point, the blue dot with white outline, which we first rotate 60 degrees around the orange dot, then 180 degrees around the green dot. Notice the end position of the blue dot is to the left of the green dot. If we start with the same starting point and pivot points but reverse the order of rotations, rotating first 180 degrees around green then 60 degrees around orange, the blue dot ends up in a totally different place, way off to the left and further down.
So once we start mixing rotations around different pivots, we have to be careful of the rotation order. Similarly, translations and rotations are not commutative with each other, so we must be careful about the relative order when we mix them together. Here, for example, if we first rotate 180 degrees around the orange dot, then translate downwards, we end up in a very different spot than if we reverse the operations, first translating downwards the same distance, then rotating 180 degrees around the orange dot.
Lastly in 2D, what about scaling, changing the size of objects? Well most commonly, we wish to scale around the X and Y axes, such that we simply multiply all coordinates by our scaling value. For example, if we wish to double the size of this triangle, we simply multiply all of its coordinate values by 2. So coordinate -3, 2 becomes -6, 4, coordinate 2, 1 becomes 4, 2, and coordinate -1, -1 becomes -2, -2. In other cases, we might wish to scale an object non-uniformly, such that the object stretches or contracts more along one axis than the other. For example, we might stretch this triangle by a factor 2 along the Y axis, but stretch it by a factor of 1 along the X axis, which is to say, stretching it not at all along the X axis. So the Y values get doubled, but the X values stay the same.
Another thing we might wish to do is scale an object around axes other than the cardinal X and Y axes of our coordinate system. To put it another way, we want to stretch the object around a center point that doesn’t lie on the origin. To accomplish this, we use the same trick we used to rotate an object around a pivot point other than the origin: we temporarily change the frame of reference by translating, then performing our operation before changing our frame of reference back.
Lastly, in other cases, we might wish to scale an object around axes that don’t run parallel to the cardinal axes, and again we can do so by temporarily changing our frame of reference, this time with a combination of translation and rotation. Here for example, we would translate our coordinates down and left, then rotate about 15 degrees before applying the scaling operation; having done our scaling, we then undo the change of reference by applying the opposite transformations in reverse order, first rotating our coordinates by about negative 15 degrees, then translating up and right.