Part Eleven: The Rotation Matrix

The rotation matrix is actually made up of three separate matrices, an X-axis rotation matrix, a Y-axis rotation matrix, and Z-axis rotation matrix. When you want to rotate something, you specify the axis that the vertices rotate around. In our example code, we’re going to make our square spin as it moves, so we want it to rotate around the Z axis, the axis that runs perpendicular to the iPhone screen.

The Z axis is associated with depth. When I move something along the X axis, it goes right as the number grows in the positive direction, and left as the number grows in the negative directions.

Likewise, if I move something along the Y axis, it goes towards the top of the screen as the number grows in the positive directions, and towards the bottom of the screen as the number grows in the negative direction.

Remember, OpenGL puts coordinate (0, 0) at the exact center of the screen.

The iPhone screen is flat, so how does the Z axis work? As Z value grows in the negative direction, the object gets farther away into the screen from you, the viewer, but as the Z value increases in the positive direction, the object gets closer to you. This is, of course, assuming that the camera is in the default position. If you move the camera, the objects will continue to move along the correct axes, but will move relative to the camera’s new position.

For example, if I rotated and moved the camera so it was looking straight down the X axis with the Y axis going up and down, things moving on the Z axis would appear to go left and right.

Later, when we add perspective to our projection matrix, you’ll see how OpenGL makes closer objects bigger, and object farther away seem smaller.

For now, let’s look at the the three different rotational matrices. First, we need to understand how the matrices are valued positionally.

 

 

Here is an identity matrix with the rows and columns labeled. Across the top, we can consider the first three columns as dealing with the position along the axis, and the fourth position our translation column. Along the left side, we can consider the rows to be coordinates.

In this example, follow the x, y, and z coordinates into the fourth column, translate. If you put x, y, and z values into the fourth column along their respective rows, you have a translation matrix.

Now follow each coordinate from the left side into the column with the same letter. If you put a value in the position where the x coordinate meets the x axis, the y coordinate meets the y axis, and the z coordinate meets the z axis, you’ve just created a scale matrix.

To make a rotational matrix, you first need to decide which axis you’re going to rotate around, and populate values for the other two. For example, if I want my rainbow square to rotate around the z axis, I’ll specify an angle of rotation, but I’ll only populate values for x and y. If I’m rotating around the z axis, then my z coordinates will not change.

In our example program so far, we don’t specify z coordinates, so they are zero. Our square has no depth, so making it spin (as opposed to making it flip) makes the most sense.

Let’s look at all of the rotation matrices first, then we’ll make some changes to our code.

 

 

To make something rotate around the X axis, we need to adjust the Y and Z coordinates. We specify an angle of rotation (in radians), and populate the matrix with the sine and cosine values of that angle. For rotating around the X axis, we will specify offsets for the Y coordinate and the Z coordinate.

 

 

To make things rotate around the Y axis, we fill in values for X and Z

 

 

And as you might guess, to make things rotate around the Z axis, we fill in X and Y values.

You might wonder why we’re feeding in sines and cosines, and why one of them is negative. Remember how the original Xcode project had a rainbow square that moved up and down? It did that because there was a transY variable that was being used to get a sine value and offset the vertices’ Y coordinates by that amount.

We added a transX variables and used it to get a cosine value, and when we started offsetting the vertices’ X coordinates by that amount, the square started to go around in a circle. A circle around the Z axis.

The rotation matrices are similar to that in concept, and the sine value that’s made negative will dictate the direction of the rotation. The way that the rotation matrices are presented here will result in clockwise rotation if you feed them positive angles of rotation.

If you flip which sine values are negative (make the positive one negative and the negative one positive), the rotation will be counter-clockwise if you feed them positive angles of rotation.

If you feed these rotation matrices negative numbers, they’ll spin the opposite direction of whatever direction they would have gone in for positive values.

Let’s change some code. In the EdMatrixTool.h file, add the following method definition (to prevent compiler warnings).

+ (void)applyRotation:(GLfloat *)m x:(GLfloat)x y:(GLfloat)y z:(GLfloat)z; // ED: Added

In the EDMatrixTools.m file, add the following method.

// ED: Added method
+ (void)applyRotation:(GLfloat *)m x:(GLfloat)x y:(GLfloat)y z:(GLfloat)z {
    GLfloat tempMatrix[16];
   
    if(x != 0) {
        GLfloat c = cosf(x);
        GLfloat s = sinf(x);
       
        [self applyIdentity:tempMatrix];
       
        tempMatrix[5] = c;
        tempMatrix[6] = -s;
        tempMatrix[9] = s;
        tempMatrix[10] = c;
       
        [self multiplyMatrix:tempMatrix by:m giving:m];
    }
   
    if(y != 0) {
        GLfloat c = cosf(y);
        GLfloat s = sinf(y);
       
        [self applyIdentity:tempMatrix];
       
        tempMatrix[0] = c;
        tempMatrix[2] = s;
        tempMatrix[8] = -s;
        tempMatrix[10] = c;
       
        [self multiplyMatrix:tempMatrix by:m giving:m];
    }
   
    if(z != 0) {
        GLfloat c = cosf(z);
        GLfloat s = sinf(z);
       
        [self applyIdentity:tempMatrix];
       
        tempMatrix[0] = c;
        tempMatrix[1] = -s;
        tempMatrix[4] = s;
        tempMatrix[5] = c;
       
        [self multiplyMatrix:tempMatrix by:m giving:m];
    }
}

This method will take x, y, and z rotational values (each causing a rotation around its own axis) and create rotation matrices for any of those values that aren’t zero.

Since some of the positions are used by more than one matrix type, we need to create individual rotation matrixes for each axis and multiply them by the passed-in matrix. By the end of this method, even if we specify values for all three axes, they’ll all have been applied to the passed-in matrix properly through matrix multiplication.

If you compare this code to the layouts of the rotation matrices shown earlier, the logic should be pretty clear.

Now let’s use this new method in our drawFrame method in the EDCubeDemoViewController.m file.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    // Replace the implementation of this method to do your own custom drawing.
    static const GLfloat squareVertices[] = {
        -0.5f, -0.33f,
        0.5f, -0.33f,
        -0.5f,  0.33f,
        0.5f,  0.33f,
    };
   
    static const GLubyte squareColors[] = {
        255, 255,   0, 255,
        0,   255, 255, 255,
        0,     0,   0,   0,
        255,   0, 255, 255,
    };
   
    static float transY = 0.0f;
    static float transX = 0.0f;
   
    GLfloat mvpMatrix[16];
   
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
   
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
        // Use shader program.
        glUseProgram(program);
       
        [EDMatrixTools applyIdentity:mvpMatrix];
       
        [EDMatrixTools applyScale:mvpMatrix x:0.5f y:0.5f z:1.0f];
        [EDMatrixTools applyRotation:mvpMatrix x:0 y:0 z:transX]; // ED: Added
        [EDMatrixTools applyTranslation:mvpMatrix x:(cosf(transX) / 2.0f) y:(sinf(transY) / 2.0f) z:0.0f];
       
        // Update uniform value.
        glUniformMatrix4fv(uniforms[UNIFORM_MVP_MATRIX], 1, 0, mvpMatrix);
        transY += 0.075f;  
        transX += 0.075f;
       
        // Update attribute values.
        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
        glEnableVertexAttribArray(ATTRIB_VERTEX);
        glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
        glEnableVertexAttribArray(ATTRIB_COLOR);
       
        // Validate program before drawing. This is a good check, but only really necessary in a debug build.
        // DEBUG macro must be defined in your debug configurations if that's not already the case.
#if defined(DEBUG)
        if (![self validateProgram:program]) {
            NSLog(@"Failed to validate program: %d", program);
            return;
        }
#endif
    }
   
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   
    [(EAGLView *)self.view presentFramebuffer];
}

All we did to this method was add one line.

[EDMatrixTools applyRotation:mvpMatrix x:0 y:0 z:transX]; // ED: Added

Now, if you run the program, the rainbow square will not only travel around the screen in a circle, it will rotate as it goes.

 

iPhone screen

 

Wait! What’s happened to our square, why is it all stretchy?

Don’t worry, nothing’s wrong with our code, but something is wrong with our program. In fact, there’s been something wrong with our program the whole time, but this is the first time we’re seeing it.

We’re going to find out what it is and fix it in the next chapter.

Part Ten | Index | Part Twelve