Lazy Foo' Productions

SDL Forums external link SDL Tutorials Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Matrix Transformations

Matrix Transformations screenshot

Last Updated: Aug 9th, 2012

Up until now, we've done texture rotation and scaling in hackish ways. In this tutorial we'll combine matrix tranformations to transform a textured quad without changing the original geometry.
From LTexture.cpp
void LTexture::render( GLfloat x, GLfloat y, LFRect* clip )
{
    //If the texture exists
    if( mTextureID != 0 )
    {
        //Texture coordinates
        GLfloat texTop = 0.f;
        GLfloat texBottom = (GLfloat)mImageHeight / (GLfloat)mTextureHeight;
        GLfloat texLeft = 0.f;
        GLfloat texRight = (GLfloat)mImageWidth / (GLfloat)mTextureWidth;

        //Vertex coordinates
        GLfloat quadWidth = mImageWidth;
        GLfloat quadHeight = mImageHeight;

        //Handle clipping
        if( clip != NULL )
        {
            //Texture coordinates
            texLeft = clip->x / mTextureWidth;
            texRight = ( clip->x + clip->w ) / mTextureWidth;
            texTop = clip->y / mTextureHeight;
            texBottom = ( clip->y + clip->h ) / mTextureHeight;

            //Vertex coordinates
            quadWidth = clip->w;
            quadHeight = clip->h;
        }

        //Move to rendering point
        glTranslatef( x, y, 0.f );

        //Set texture ID
        glBindTexture( GL_TEXTURE_2D, mTextureID );

        //Render textured quad
        glBegin( GL_QUADS );
            glTexCoord2f(  texLeft,    texTop ); glVertex2f(       0.f,        0.f );
            glTexCoord2f( texRight,    texTop ); glVertex2f( quadWidth,        0.f );
            glTexCoord2f( texRight, texBottom ); glVertex2f( quadWidth, quadHeight );
            glTexCoord2f(  texLeft, texBottom ); glVertex2f(       0.f, quadHeight );
        glEnd();
    }
}
The LTexture render() function has returned to it's pre stretching/rotating state with the origin of the textured quad at the top left. What different this time is that there's no call to glLoadIdentity() in this version of render(). If we did that, any transformations made before this call would be pointless.

In modern OpenGL applications, vertex data is set in chunks (known as vertex buffers) to the GPU. It's inefficient to send different vertex data over and over again when we're essentially rendering the same quad that's just transformed in different ways. So it's important to know how to get the results you need without sending redundant vertex data.
From LUtil.cpp
//OpenGL texture
LTexture gRotatingTexture;

//Rotation Angle
GLfloat gAngle = 0.f;

//Transformation state
int gTransformationCombo = 0;
At the top of LUtil.cpp we have a couple global variables. We have our rotating texture, the angle at which it will rotate, and lastly the transformation state.

In this tutorial, we'll be using different sets of matrix transformations. "gTransformationCombo" is just a variable to indicate which one we're using right now.
From LUtil.cpp
bool loadMedia()
{
    //Load texture
    if( !gRotatingTexture.loadTextureFromFile( "13_matrix_transformations/texture.png" ) )
    {
        printf( "Unable to load OpenGL texture!\n" );
        return false;
    }

    return true;
}

void update()
{
    //Rotate
    gAngle += 360.f / SCREEN_FPS;

    //Cap angle
    if( gAngle > 360.f )
    {
        gAngle -= 360.f;
    }
}
As with last time, loadMedia() loads are texture and update() updates our angle of rotation.
From LUtil.cpp
void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );

    //Reset transformation
    glLoadIdentity();

    //Render current scene transformation
    switch( gTransformationCombo )
    {
At the top of our render() we clear the color buffer then load the identity matrix. Remember that the LTexture render() function no longer calls glLoadIdentity(), so now we have to call it before we render our textured quad.

Then, depending on which transformation combination is selected, we apply some tranformations to the modelview matrix.
From LUtil.cpp
        case 0:
            glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
            glRotatef( gAngle, 0.f, 0.f, 1.f );
            glScalef( 2.f, 2.f, 0.f );
            glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f );
        break;
Here's the main transformation of the demo: making the double sized textured quad spin around it's center at the center of the screen. Let's step through this transformation to see how it works. First here's our untransformed textured quad:
step0

First thing we do is translate to the center of the screen:
step1
Since the origin the quad is at the top left, the top left corner is touching the center of the screen.

Then we rotate the quad:
step2
Remember: glRotate() rotates around the current point of rotation.

Since we want our quad to be double in size, we scale it using glScale():
step3
The arguments in this call for glScale() say that we want to double it in size along the x and y axis.

We want the quad to rotate around its center, so we translate back half the span of the quad:
step4

It's important to know when you apply a transformation, it transforms the transformations after it. Since we called glRotate before we translated half the span of the quad, the translation was also rotated. Since we called glScale to stretch the quad, the translation of half the span of the quad was also scaled.

A side note about order of operations: notice that the negation sign is by the float in gRotatingTexture.imageWidth() / -2.f. The function imageWidth() returns an unsigned int, which with a negation sign produces undesired results.
From LUtil.cpp
        case 1:
            glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
            glRotatef( gAngle, 0.f, 0.f, 1.f );
            glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f );
            glScalef( 2.f, 2.f, 0.f );
        break;
In this combination, we call glScale() last. What this does is when we the translate of half the span of the quad, the translation isn't scaled so the quad rotation is off.
From LUtil.cpp
        case 2:
            glScalef( 2.f, 2.f, 0.f );
            glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
            glRotatef( gAngle, 0.f, 0.f, 1.f );
            glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f );
        break;
In this combination we call glScale() first, which causes the quad to spin around its center but since the initial translation to the center of the screen is also scaled, the quad spins around its center at the bottom right corner of the screen.
From LUtil.cpp
        case 3:
            glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
            glRotatef( gAngle, 0.f, 0.f, 1.f );
            glScalef( 2.f, 2.f, 0.f );
        break;
In this combination, we don't translate of half the span of the quad which causes the quad to spin around its top left corner.
From LUtil.cpp
        case 4:
            glRotatef( gAngle, 0.f, 0.f, 1.f );
            glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
            glScalef( 2.f, 2.f, 0.f );
            glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f );
        break;
    }
In this combination we rotate first, which cause the quad to rotate around the top left corner of the screen.
From LUtil.cpp
    //Render texture
    gRotatingTexture.render( 0.f, 0.f );

    //Update screen
    glutSwapBuffers();
}
Finally after applying our transformations we render our transformed quad.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y )
{
    //If q is pressed
    if( key == 'q' )
    {
        //Reset rotation
        gAngle = 0.f;

        //Cycle through combinations
        gTransformationCombo++;
        if( gTransformationCombo > 4 )
        {
            gTransformationCombo = 0;
        }
    }
}
In our key handler, when the user presses q we reset the angle of rotation and cycle through the transformation combinations.