Basic transformations

About the code

You can read here a guide to install this sofware.

Although it is based on SDL, I don't use its functions directly. I have written a small library with a few basic

functions to ease the understanding and the portability to another language.

You can read more about this lib here.

This article uses the line function we added in the Bresenham's line algorithm.

And the Vec2f class we added in vectors.

Shapes

But to visualize them clearly we will need to draw a simple shape.

I chose a triangle centered around the origin.

As we said before, a point can be seen as a vector. So we will use the Vec2f class I introduced in the 2 last

articles.

We will see that applying a transformation to our triangle is the same as applying it to each of its points.

But first let's draw our triangle. In the code it is defined as an array:

// define the shape
Vec2f shape[] =
{
{-20, 20}, {-20, -20}, {30, 20}
};

And we draw it as if the origin is at the center of the screen
void drawShape(Vec2f* points, int nbPoints, Color color)
{
for (int i = 0; i < nbPoints; i++)
{
int next = (i + 1) % nbPoints;
gfx.line(points[i].x + SCREEN_WIDTH / 2,
points[i].y + SCREEN_HEIGHT / 2,
points[next].x + SCREEN_WIDTH / 2,
points[next].y + SCREEN_HEIGHT / 2,
color);
}
}
Download source code
Download executable for Windows

And here is the result:

Translation

We simply need to add the translation vector to each point.

We don't need to create a special function in Vec2f, we will use the addition operator to add the vector to each

point of the shape

void translateShape(Vec2f* points, int nbPoints, Vec2f transVector)
{
for (int i = 0; i < nbPoints; i++)
points[i] += transVector;
}

And in our main loop we will make a copy of our shape, apply the transformation, then draw it:
while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShape(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// translate and draw
translateShape(shape2, 3, Vec2f(150, 75));
drawShape(shape2, 3, Color(255, 0, 0));
gfx.render();
sys.wait(20);
sys.processEvents();
}
Download source code
Download executable for Windows

Scaling

The Vec2f class allows us to multiply a vector by a single float. But if we want to have a different scale along x

and y we need to write a new function:

void Vec2f::scale(const Vec2f& s)
{
x *= s.x;
y *= s.y;
}

Now if we modify our main loop to use this function on the copy of our shape
void scaleShape(Vec2f* points, int nbPoints, Vec2f scaleVector)
{
for (int i = 0; i < nbPoints; i++)
points[i].scale(scaleVector);
}
*[...]*
while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShape(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// scale and draw
scaleShape(shape2, 3, Vec2f(6, 3));
drawShape(shape2, 3, Color(255, 0, 0));
Download source code
Download executable for Windows

We get this:We can also use negative values to get an horizontal or vertical symetry

// scale and draw
scaleShape(shape2, 3, Vec2f(-6, 3));
drawShape(shape2, 3, Color(255, 0, 0));
Download source code
Download executable for Windows

Or negative values on both axes to get a central symetry

// scale and draw
scaleShape(shape2, 3, Vec2f(-6, -3));
drawShape(shape2, 3, Color(255, 0, 0));
Download source code
Download executable for Windows

Rotation

In the article about circles, we already saw how to compute the image of a point that is on the horizontal axis.

But how do we transform a point that can be anywhere on the plane ?

We will decompose it's coordinates to an horizontal and a vertical part:

We already know how to rotate the horizontal part but what is the image of the vertical one ?

Visually, by looking at the greatest and the smallest coordinates, and correlating them with the image of the

horizontal part, we can guess the coordinates of this point:

So the equations to rotate our point are:

newX = x * cos(angle) - y * sin(angle);
newY = x * sin(angle) + y * cos(angle);

We can add that as a new function in our Vec2f class:
void Vec2f::rotate(const float angle)
{
float co = cos(angle);
float si = sin(angle);
float newX = x * co - y * si;
float newY = x * si + y * co;
x = newX;
y = newY;
}

Now let's test that with our shape
void rotateShape(Vec2f* points, int nbPoints, float angle)
{
for (int i = 0; i < nbPoints; i++)
points[i].rotate(angle);
}
*[...]*
while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShape(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// rotate and draw
rotateShape(shape2, 3, DEG_TO_RAD(30));
drawShape(shape2, 3, Color(255, 0, 0));
Download source code
Download executable for Windows

And this is what we see:Notice that the triangle rotates in the clockwise direction because on the screen, the y coordinate increases

towards the bottom, when mathematicians usually make it increase towards the up.

Combining transformations

For example let's combine a translation and a scaling.

In one case we will do the scale first then the translation, and in the other case we will do the opposite.

while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShape(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// transform and draw
scaleShape(shape2, 3, Vec2f(-2, 1));
translateShape(shape2, 3, Vec2f(100, 50));
drawShape(shape2, 3, Color(0, 255, 0));
// copy shape
Vec2f shape3[3];
for (int i = 0; i < 3; i++)
shape3[i] = shape[i];
// transform and draw
translateShape(shape3, 3, Vec2f(100, 50));
scaleShape(shape3, 3, Vec2f(-2, 1));
drawShape(shape3, 3, Color(255, 0, 0));
Download source code
Download executable for Windows

And this is what we get:For the green triangle, we first scaled it...

...then we tranlated it:

The red one was first translated...

...then scaled.

If we combine a rotation and a translation we get different results too.

float angle = 0;
// wait until we quit
while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShape(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// transform and draw
rotateShape(shape2, 3, DEG_TO_RAD(angle));
translateShape(shape2, 3, Vec2f(150, 0));
drawShape(shape2, 3, Color(0, 255, 0));
// copy shape
Vec2f shape3[3];
for (int i = 0; i < 3; i++)
shape3[i] = shape[i];
// transform and draw
translateShape(shape3, 3, Vec2f(150, 0));
rotateShape(shape3, 3, DEG_TO_RAD(angle));
drawShape(shape3, 3, Color(255, 0, 0));
angle += 1;
Download source code
Download executable for Windows

In this animated program the green triangle spins at a fixed point while the red one turns around the screen.

Changing the center of rotation

A rotation is always applied around the origin of the plane. So in the case of our triangle it lies inside it as we

can see on the first image of this article.

Now suppose that we want the triangle to rotate around the right angled vertex - that is the coordinate of the

first point in the array.

We can do that by first translating the triangle so that this point gets to the origin.

Then we rotate the triangle.

And finally we translate it back.

With this code you will see the diference between the 2 centers of rotation.

void drawShapeLeft(Vec2f* points, int nbPoints, Color color)
{
for (int i = 0; i < nbPoints; i++)
{
int next = (i + 1) % nbPoints;
gfx.line(points[i].x + SCREEN_WIDTH / 3,
points[i].y + SCREEN_HEIGHT / 2,
points[next].x + SCREEN_WIDTH / 3,
points[next].y + SCREEN_HEIGHT / 2,
color);
}
}
void drawShapeRight(Vec2f* points, int nbPoints, Color color)
{
for (int i = 0; i < nbPoints; i++)
{
int next = (i + 1) % nbPoints;
gfx.line(points[i].x + SCREEN_WIDTH * 2 / 3,
points[i].y + SCREEN_HEIGHT / 2,
points[next].x + SCREEN_WIDTH * 2 / 3,
points[next].y + SCREEN_HEIGHT / 2,
color);
}
}
void translateShape(Vec2f* points, int nbPoints, Vec2f transVector)
{
for (int i = 0; i < nbPoints; i++)
points[i] += transVector;
}
void scaleShape(Vec2f* points, int nbPoints, Vec2f scaleVector)
{
for (int i = 0; i < nbPoints; i++)
points[i].scale(scaleVector);
}
void rotateShape(Vec2f* points, int nbPoints, float angle)
{
for (int i = 0; i < nbPoints; i++)
points[i].rotate(angle);
}
int main(int argc, char* argv[])
{
// init the window
gfx.init("2D Transforms 9", SCREEN_WIDTH, SCREEN_HEIGHT);
gfx.init2D();
// define the shape
Vec2f shape[] =
{
{-20, 20}, {-20, -20}, {30, 20}
};
float angle = 0;
// wait until we quit
while (sys.isQuitRequested() == false)
{
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
drawShapeLeft(shape, 3, Color(0, 255, 255));
drawShapeRight(shape, 3, Color(0, 255, 255));
// copy shape
Vec2f shape2[3];
for (int i = 0; i < 3; i++)
shape2[i] = shape[i];
// transform and draw
rotateShape(shape2, 3, DEG_TO_RAD(angle));
drawShapeLeft(shape2, 3, Color(0, 255, 0));
// copy shape
Vec2f shape3[3];
for (int i = 0; i < 3; i++)
shape3[i] = shape[i];
// transform and draw
translateShape(shape3, 3, -shape[0]);
rotateShape(shape3, 3, DEG_TO_RAD(angle));
translateShape(shape3, 3, shape[0]);
drawShapeRight(shape3, 3, Color(255, 0, 0));
angle += 1;
gfx.render();
sys.wait(20);
sys.processEvents();
}
gfx.quit();
return EXIT_SUCCESS;
}
Download source code
Download executable for Windows

Links