Thursday, February 28, 2013

3D transformations in OpenGL

    Let's take a look at 3D!  A big selling point of OpenGL (in my opinion) is its ability to draw 3D graphics with just a few commands.  Adding a dimension has some interesting side effects: translating and scaling can now be done on an additional axis, and rotation can now use any 3D axis rather than just the z-axis.  This post should cover how to setup a 3D scene and use some transformations.

Perspective
    For 2D scenes, the function gluOrtho2D helped setup the coordinate system.  Now, we need a function which can setup a 3D coordinate system; we need the function gluPerspective.  Documentation for it can be found at http://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml, but basically it takes 4 arguments: field of view, aspect ratio, near distance, and far distance.

    The field of view is the amount of the world that the camera can see at any moment.  It is an angle measured in degrees.  The aspect ratio is the window width divided by its height.  This is also accounted for in previous 2D examples, but here we only need to provide one value.  The near distance is the closest distance at which shapes are drawn, and the far distance is the maximum distance for drawing shapes.  These are both adjustable, but its best to keep them above zero and not ridiculously far apart.

    This function will create a 3D camera at the position (0, 0, 0), so in order to see shapes drawn, we must move them in front of the camera.  When using gluPerspective, negative values of Z are in front of the camera (into the monitor) while positive Z is behind the camera (out of the monitor).  The following example will draw a plane and place it in front of the camera:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*translation amounts (negative Z in front of camera)*/
float shiftX = 0, shiftY = -1, shiftZ = -3;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    /*print proposed movement of shape*/
    printf("will move shape to new position");
    printf("(%g, %g, %g)\n", shiftX, shiftY, shiftZ);

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*move shape in front of (and slightly below) camera*/
       glLoadIdentity();
       glTranslatef(shiftX, shiftY, shiftZ);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will move shape to new position(0, -1, -3)

output inside window:
A Quadrilateral Drawn in 3D


    First, three float variables are declared which are used later for glTranslatef.  The shiftZ is negative in order to move the shape in front of the camera.  These variables will form the position that the shape is drawn around, so the point (0, -1, -3) is at the center of this square.  Also, the point (-1, -1, -4) is at the far, left corner since the square's point (-1, 0, -1) will be shifted by -1 in the Y direction and -3 in the Z.

    The function gluPerspective is given the field of view 70.  This is a personal preference, so feel free to change this value and observe the result.  The given aspect ratio (1.333) is accurate for a 640 by 480 window, so if you use different window dimensions, just divide the width by the height.

    The near and far values are also personal preferences, but if the near value is changed to 3, then the closer half of the plane is no longer visible.  This is because nothing closer than the near distance is drawn, and this is called clipping.  Alternatively, if the far value is set to 3, then the farther half of the plane is removed.  This is because it is farther than the given far distance.

Rotations
    Honestly, the previous example is just a roundabout way of drawing a trapezoid.  So, one way to showcase the 3D aspect of it would be to include some interactive rotation.  The following example will rotate the plane based on SDL mouse position:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*SDL mouse coordinates*/
int mouseX, mouseY;

/*rotation angles*/
float angleX = 0.0, angleY = 0.0;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
          if (input.type == SDL_KEYDOWN) { /*exit with escape key*/
              if (input.key.keysym.sym == SDLK_ESCAPE) loop = 0;
          }
       }

       SDL_GetMouseState(&mouseX, &mouseY);

       /*move shape directly in front of camera*/
       glLoadIdentity();
       glTranslatef(0, 0, -3);

       /*rotate about Y axis based on mouseX*/
       angleY = 360.0 * (float)mouseX / 640.0;
       glRotatef(angleY, 0, 1, 0);

       /*rotate about X axis based on mouseY*/
       angleX = 180.0 * (float)mouseY / 480.0;
       glRotatef(angleX, 1, 0, 0);

       /*printf rotations*/
       printf("X-axis: %g degrees, Y-axis: %g degrees\n", angleX, angleY);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
X-axis: 16.125 degrees, Y-axis: 73.6875 degrees
X-axis: 25.5 degrees, Y-axis: 102.375 degrees
X-axis: 27 degrees, Y-axis: 106.875 degrees
X-axis: 28.125 degrees, Y-axis: 110.813 degrees
X-axis: 28.875 degrees, Y-axis: 113.625 degrees
X-axis: 30 degrees, Y-axis: 117 degrees
X-axis: 30.75 degrees, Y-axis: 120.375 degrees
X-axis: 31.5 degrees, Y-axis: 122.625 degrees
X-axis: 32.625 degrees, Y-axis: 126.563 degrees
X-axis: 33 degrees, Y-axis: 128.25 degrees
X-axis: 34.125 degrees, Y-axis: 131.625 degrees
X-axis: 34.875 degrees, Y-axis: 135 degrees
X-axis: 36.375 degrees, Y-axis: 139.5 degrees
X-axis: 37.875 degrees, Y-axis: 144 degrees
X-axis: 38.625 degrees, Y-axis: 146.813 degrees
X-axis: 40.125 degrees, Y-axis: 150.75 degrees
X-axis: 40.875 degrees, Y-axis: 152.438 degrees
X-axis: 42.375 degrees, Y-axis: 156.938 degrees
X-axis: 43.5 degrees, Y-axis: 160.875 degrees
X-axis: 45 degrees, Y-axis: 165.375 degrees
X-axis: 45.375 degrees, Y-axis: 167.625 degrees
X-axis: 46.5 degrees, Y-axis: 171 degrees
X-axis: 46.875 degrees, Y-axis: 173.813 degrees
X-axis: 47.625 degrees, Y-axis: 177.188 degrees
X-axis: 48 degrees, Y-axis: 180 degrees
X-axis: 48.75 degrees, Y-axis: 184.5 degrees
X-axis: 49.125 degrees, Y-axis: 188.438 degrees
X-axis: 49.125 degrees, Y-axis: 190.125 degrees
X-axis: 51 degrees, Y-axis: 197.438 degrees
X-axis: 51.375 degrees, Y-axis: 201.375 degrees
X-axis: 52.125 degrees, Y-axis: 203.063 degrees
X-axis: 52.5 degrees, Y-axis: 208.688 degrees
X-axis: 52.875 degrees, Y-axis: 212.063 degrees
X-axis: 53.25 degrees, Y-axis: 215.438 degrees
X-axis: 53.625 degrees, Y-axis: 216.563 degrees
X-axis: 53.625 degrees, Y-axis: 218.25 degrees
X-axis: 54 degrees, Y-axis: 219.375 degrees
X-axis: 54 degrees, Y-axis: 222.188 degrees
X-axis: 54.375 degrees, Y-axis: 224.438 degrees
X-axis: 54.75 degrees, Y-axis: 226.688 degrees
X-axis: 54.75 degrees, Y-axis: 226.688 degrees
X-axis: 54.75 degrees, Y-axis: 227.25 degrees
X-axis: 54.75 degrees, Y-axis: 228.938 degrees
X-axis: 54.75 degrees, Y-axis: 230.625 degrees
X-axis: 54.75 degrees, Y-axis: 231.75 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees

output inside window:
Rotated Quadrilateral


    Here glClear has been turned off again, so if you don't want the after images seen in the picture above, be sure to uncomment the glClear line.  The picture above is based on me moving the mouse toward the yellow corner until they overlap.  Notice that the example exits if the escape key is pressed; this was to stop the printf statements without having to click the upper-right close button (which still works).

    SDL_GetMouseState is used to get the mouse coordinates and save them to the mouseX and mouseY variables.  For every frame, these coordinates are used to calculate the angleX and angleY values.  Notice that mouseX and mouseY are divided by the window width and height respectively.  These will give us values between 0 and 1 which will give us angles between 0 and 360 degrees or 0 and 180 degrees when multiplied.

    For this example, the shape is first rotated around the X axis, then the Y axis, and finally translated into position.  Remember that OpenGL performs transformations opposite the order we write them.  If you were to switch the order of the X and Y rotations, the result would be changed.  It is hard to explain, but the "feel" of the rotation is different, so I highly recommend running this example and swapping the order of rotations to try it out.

    When working with rotations, be cautious of gimbal lock.  This example rotates about the X and Y axes, and the order of the rotations affects the result.  I believe the axis of the second rotation operation is rotated by the first, so if I rotate 90 degrees on the Y axis, any following X axis rotation would actually be a Z axis rotation.  For more information about gimbal lock, see http://en.wikipedia.org/wiki/Gimbal_lock.

Moving the Camera
    So far, shapes have been placed in front of the point (0, 0, 0) so that they can be visible.  However, we can also have a "moving" camera by applying the same set of transformations to every shape.  For a large, 3D scene, every shape must be rotated and translated in terms of the camera's position and angles.  While we could use glRotatef and glTranslatef, there are situations where the function gluLookAt can help us more.

    This function takes 9 arguments, which are grouped into sets of three.  The first three are the x, y, and z of the camera's position.  The second three are the x, y, and z of the point which the camera is focused on.  The last three make up a three-dimensional direction for the camera, the up vector.  This is usually (0, 1, 0), but can be any other direction if you wanted to tilt the camera sideways or upside down.

    We still need a way to save and restore the current matrix.  If we setup a matrix which puts everything around the camera, we still need to be able to scale, rotate and translate individual shapes without them affecting each other.  The matrix created from gluLookAt can be saved with "glPushMatrix();" and can become the current modelview matrix again by using "glPopMatrix();".

    The following example will use gluLookAt to move the camera and draw rectangles at (0, 0, 0) and (-5, 0, 0):
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*camera position*/
float camX = 2, camY = 2, camZ = 2;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print camera position*/
    printf("camera position = (%g, %g, %g)\n", camX, camY, camZ);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*move camera away from shape, rotate to see shape*/
       glLoadIdentity();
       gluLookAt(camX, camY, camZ,  0, 0, 0,  0, 1, 0);

       /*transformations which work in place of gluLookAt*/
       /*glRotatef(35.26, 1, 0, 0); /*x-axis rotation*/
       /*glRotatef(-45, 0, 1, 0); /*y-axis rotation*/
       /*glTranslatef(-2, -2, -2); /*move to position*/

       /*scale and translate the shape*/
       glPushMatrix(); /*save current modelview*/
       glTranslatef(-5, 0, 0);
       glScalef(2, 1, 2);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*light teal square*/
       glColor3ub(0, 255, 255);
       glVertex3f(-1, 0, -1);
       glVertex3f(-1, 0, 1);
       glVertex3f(1, 0, 1);
       glVertex3f(1, 0, -1);

       glEnd();
       glPopMatrix(); /*restore previous modelview*/

       /*scale the shape*/
       glPushMatrix(); /*save current modelview*/
       glScalef(2, 1, 1);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();
       glPopMatrix(); /*restore previous modelview*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
camera position = (2, 2, 2)

output inside window:
Two Planes seen from a 3D Camera


    Every frame, the modelview matrix is set to the identity matrix and changed using the gluLookAt function.  Then, each shape is transformed using the result of gluLookAt and some unique transformations (scaling and translating in this case).  The unique transformations are applied after glPushMatrix and are removed by calling glPopMatrix.

    In this example, gluLookAt is told that the camera is at position (2, 2, 2) and looking at point (0, 0, 0).  This same transformation can be accomplished with two glRotatef calls and a glTranslatef as seen by the commented lines.  To test this, you can comment the gluLookAt line and un-comment the three OpenGL functions below it.  Note that these three lines translate then rotate (and don't scale), reversing the usual order of transformations.

    After the camera transformations are in the modelview matrix, we can still apply transformations to individual shapes.  The teal rectangle is scaled then translated in order to have a 4x4 plane centered at (-5, 0, 0).  The multi-colored rectangle is scaled to be 4x2 and remains centered at (0, 0, 0).  These individual transformations can be applied after the camera transformations.

    However, we don't want to move the multi-colored rectangle to (-5, 0, 0) or scale it beyond 4x2.  That means we need a way to remove the "glTranslatef(-5, 0, 0);" and "glScalef(2, 1, 2);" calls used for the teal rectangle.  We could apply "glScalef(0.5, 1, 0.5);" and glTranslatef(+5, 0, 0);", the opposite transformations in the opposite order.

    Since OpenGL provides the functions glPushMatrix and glPopMatrix, we can avoid calculating the reverse transformation needed to restore the result of gluLookAt.  glPushMatrix can store the current modelview matrix and glPopMatrix will remove the current modelview matrix and replace it with what was saved earlier.  Without the first call to glPopMatrix, the multi-colored rectangle would be transformed using gluLookAt, glTranslatef, and every call to glScalef.

    To wrap up this post, here are better explanations of the way matrices help transform shapes: http://robertokoci.com/world-view-projection-matrix-unveiled/ and http://3dgep.com/?p=1700.   For OpenGL, there are separate projection and modelview matrices, but the modelview can also be split into a world matrix (individual transformations) and a view matrix (camera transformations).  Also, here is the documentation for gluLookAt: http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml.  Finally, another tutorial page can be found at http://www.swiftless.com/tutorials/opengl/rotation.html, and it is written in C++ using GLUT rather than C using SDL.

    Thank you for reading, and future posts should discuss lighting and depth testing.

Monday, February 18, 2013

OpenGL Transformations

    Another feature of OpenGL is that it can make changes to shapes without us adjusting any vertex positions.  The three basic changes that can be applied are scaling, rotating, and translating.  OpenGL uses a 4x4 matrix on shapes to modify each of its vertices, or points.  This post should cover each basic transformation in two dimensions.

Scaling
    If we wanted to re-size a shape, we could change the vertex positions to make a bigger shape.  However, OpenGL offers the function glScalef which will use a matrix to re-size input shapes along the x, y, and z axes.  The following example will only use the x and y axes for a two-dimensional rectangle:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*scale factors*/
float scaleX = 3;
float scaleY = 2;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print the scale factors*/
    printf("will scale shape %g times on the x-axis\n", scaleX);
    printf("and %g times on the y-axis\n", scaleY);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix to scale shape*/
       glLoadIdentity();
       glScalef(scaleX, scaleY, 1);

       /*set color to yellow*/
       glColor3f(1, 1, 0);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*square*/
       glVertex2f(-1, -1);
       glVertex2f(-1, 1);
       glVertex2f(1, 1);
       glVertex2f(1, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will scale shape 3 times on the x-axis
and 2 times on the y-axis

output inside window:
A Square scaled to a Rectangle


    The function glMatrixMode will accept the enumerations GL_PROJECTION, GL_MODELVIEW, and two others.  OpenGL uses two matrices to modify shapes: a ModelView matrix and a Projection matrix.  The projection matrix is mostly used to help display the shape, especially three dimensional shapes.  For this example, it maps the shape's points to their pixel positions (similar to the coordinates used in SDL).

    The function glLoadIdentity requires no arguments, but will set the specified matrix (modelview or projection) to the identity matrix.  Any additional functions like gluOrtho2D or glScalef will multiply this identity matrix by an additional matrix.  The result will be the new matrix since any matrix multiplied by the identity matrix is the same matrix (hence the name identity matrix).

    For this example, the screen is mapped from -4 on the left to 4 on the right as well as -3 on the bottom to 3 on the top.  The specified shape is a 2x2 square in the center, yet the final picture shows a 6x4 rectangle.  Before the projection matrix maps the shape points to their correct pixel positions, the modelview matrix is applied, which in this case scales horizontally by 3 and by 2 along the y-axis.

    As a demonstration of the math, here is a diagram which multiples a shape point by the modelview matrix:


    This modelview matrix would be the result of the glScalef operation.  Note that the example above does scale along the z-axis by 1.  If the modelview matrix were not "scaled", the 3 and 2 would instead be 1's and the modelview matrix would be the identity matrix.  Also, notice that the point consists of an x and y (which are given by glVertex2f), a 0 for z (since its two dimensional), and an additional 1 at the end.  This extra 1 will prove useful later.

    Previous examples used the identity matrix as the modelview matrix, which meant the input points were mapped according to the projection matrix.  This example will scale each point to produce a different shape, then apply the project matrix to the new shape.  This intermediate step allows us to make easy changes to a shape with any number of points since OpenGL will automatically apply the transformation to every vertex.

Rotating
    OpenGL also allows us to rotate shapes before they are displayed on screen.  This transformation is the most complex mathematically, especially in three dimensions.  It requires that we specify an angle (in degrees) and an axis to rotate around.  For two-dimensional rotation, we can use the z-axis.

    The z-axis in OpenGL is perpendicular to the screen.  If you imagine a pole running straight through your monitor, that would represent the z-axis.  you could turn the monitor clockwise or counter-clockwise around this pole by a certain number of degrees.  For this example, the drawn square has a similar pole, which is specified by the z-axis (or vector <0,0,1>):
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*degrees of rotation*/
float angle = 0;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix to rotate shape*/
       glLoadIdentity();
       glRotatef(angle, 0, 0, 1); /*along z-axis*/

       /*print rotation for current frame*/
       printf("drawing shape at %g degrees\n", angle);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex2f(-1, -1);
       glColor3f(1, 0, 0); glVertex2f(-1, 1);
       glColor3f(0, 1, 0); glVertex2f(1, 1);
       glColor3f(0, 0, 1); glVertex2f(1, -1);

       glEnd();
       angle += 1; /*adjust angle for next frame*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
drawing shape at 0 degrees
drawing shape at 1 degrees
drawing shape at 2 degrees
drawing shape at 3 degrees
drawing shape at 4 degrees
drawing shape at 5 degrees
drawing shape at 6 degrees
drawing shape at 7 degrees
drawing shape at 8 degrees
drawing shape at 9 degrees
drawing shape at 10 degrees
drawing shape at 11 degrees
drawing shape at 12 degrees
drawing shape at 13 degrees
drawing shape at 14 degrees
drawing shape at 15 degrees
drawing shape at 16 degrees
drawing shape at 17 degrees
drawing shape at 18 degrees
drawing shape at 19 degrees
drawing shape at 20 degrees
drawing shape at 21 degrees
drawing shape at 22 degrees
drawing shape at 23 degrees
drawing shape at 24 degrees
drawing shape at 25 degrees

output inside window:
A Square Rotated over Time


    The function glRotatef uses the inputs angle, x, y, and z (in that order).  The angle is in degrees and can be negative to rotate the opposite way.  The x, y, and z make up the axis or vector we wish to rotate about.  The length of the input vector does not affect the rotation, so we could use <0,0,5>.  However, if we change the vector to <0,0,-1>, we have also changed the direction, which would reverse the way the square is rotated.

    Notice that the glClear call is commented out.  This is to show the rotation of the square over time.  Feel free to un-comment this line when running the example.  For this example, the square starts out aligned to the x and y axes, then rotates counter-clockwise until the program is ended.  The picture above should show a rotation from 0 to 25 degrees, with each new square drawn on top of the previous.

    While OpenGL does allow is to rotate around any axis we input, the following diagram only shows rotations around the z-axis (or two-dimensional rotations):

    The alpha symbol represents the angle we wish to rotate by.  If we wanted to rotate 45 degrees, to make the square look like a diamond, the new point would have an x of 0 and a y of root 2.  Basically, the upper-right corner would be moved to the center and moved up slightly.  Since a similar process is done for every corner, the end result is a rotation of 45 degrees.

Translating
    The final transformation is referred to as translation, which means moving or shifting the shape.  This requires the function glTranslatef, which requires an x, y, and z input.  These float variables will specify how much to move the shape in each direction.  They can be negative to indicate a movement in the opposite direction.

    For two-dimensional shapes, we only need to use x and y inputs and pass 0 to z.  The following example will use translation with scaling and rotating:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*scale variables*/
float scaleX = 2, scaleY = 1.5;
/*rotation variable*/
float angle = 0;
/*translation variables*/
float shiftX = 1, shiftY = 1;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print scaling variables*/
    printf("will scale shape %g times on the x-axis\n", scaleX);
    printf("and %g times on the y-axis\n", scaleY);

    /*print translating variables*/
    printf("will move shape to (%g,%g)\n", shiftX, shiftY);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix*/
       glLoadIdentity();
       glTranslatef(shiftX, shiftY, 0); /*move shape*/
       glRotatef(angle, 0, 0, 5); /*along z-axis*/
       glScalef(scaleX, scaleY, 1); /*re-size shape*/

       /*print rotation for current frame*/
       printf("drawing shape at %g degrees\n", angle);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex2f(-1, -1);
       glColor3f(1, 0, 0); glVertex2f(-1, 1);
       glColor3f(0, 1, 0); glVertex2f(1, 1);
       glColor3f(0, 0, 1); glVertex2f(1, -1);

       glEnd();
       angle += 1; /*adjust angle for next frame*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will scale shape 2 times on the x-axis
and 1.5 times on the y-axis
will move shape to (1,1)
drawing shape at 0 degrees
drawing shape at 1 degrees
drawing shape at 2 degrees
drawing shape at 3 degrees
drawing shape at 4 degrees
drawing shape at 5 degrees
drawing shape at 6 degrees
drawing shape at 7 degrees
drawing shape at 8 degrees
drawing shape at 9 degrees
drawing shape at 10 degrees
drawing shape at 11 degrees
drawing shape at 12 degrees
drawing shape at 13 degrees
drawing shape at 14 degrees
drawing shape at 15 degrees
drawing shape at 16 degrees
drawing shape at 17 degrees
drawing shape at 18 degrees
drawing shape at 19 degrees
drawing shape at 20 degrees
drawing shape at 21 degrees
drawing shape at 22 degrees
drawing shape at 23 degrees
drawing shape at 24 degrees

output inside window:
A Transformed Rectangle


    While we can apply all three transformations, we must be careful to do so in the right order.  The ideal order is scale, rotate, translate (as introduced in this post).  Were we to rotate after translating, the shape would rotate around the origin rather than its own origin; and if we rotate then scale in this example, the shape would be stretched horizontally at all times and appear to have a bad aspect ratio.

    Despite my opinion that scale, rotate, then translate is the ideal order, the example shows the commands in the order translate, rotate, then scale.  This is because OpenGL reverses the order of the transformations when creating matrices (except glLoadIdentity).  It can be tricky, so be careful to remember this quirk of OpenGL.

    So, onto translation.  This is where the extra 1 at the end of our points comes in handy: were it not for this 1, moving the shape with matrices would be very difficult.  Here is a diagram which translates one of the square's points:

    The column on the right of the modelview matrix consists of the x, y, and z we wish to move the shape by.  Each of these are multiplied by the 1 at the end of the point and added to the point's x, y, or z.  For this case, the 2d point (1, -1) is moved to (2, 0) since an x shift of 1 and a y shift of 1 was used.

    With that, I will wrap up this post.  For documentation on OpenGL transformations, see http://www.opengl.org/sdk/docs/man2/xhtml/glScale.xml, http://www.opengl.org/sdk/docs/man2/xhtml/glRotate.xml, and http://www.opengl.org/sdk/docs/man2/xhtml/glTranslate.xml.  For more information about rotation matrices, see the page http://en.wikipedia.org/wiki/Rotation_matrix.  For a more in-depth tutorial, check out http://www.videotutorialsrock.com/opengl_tutorial/transform/text.php.

    Thanks again for reading; the next post will discuss how to setup a 3D camera and use 3D transformations!

P.S. I had to change the matrix diagrams from transparent GIF's to PNG's with a white background for better readability.  Also, I fixed some awkward spacing .