Chapter 3. OpenGL's Moving Triangle
Prev | Part II. Positioning | Next

## Chapter 3. OpenGL's Moving Triangle

This tutorial is about how to move objects around. It will introduce new shader techniques.

## Moving the Vertices

The simplest way one might think to move a triangle or other object around is to simply modify the vertex position data directly. From previous tutorials, we learned that the vertex data is stored in a buffer object. So the task is to modify the vertex data in the buffer object. This is what `cpuPositionOffset.cpp` does.

The modifications are done in two steps. The first step is to generate the X, Y offset that will be applied to each position. The second is to apply that offset to each vertex position. The generation of the offset is done with the `ComputePositionOffset` function:

Example 3.1. Computation of Position Offsets

    void ComputePositionOffsets(float &fXOffset, float &fYOffset)
    {
        const float fLoopDuration = 5.0f;
        const float fScale = 3.14159f * 2.0f / fLoopDuration;

        float fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;

        float fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);

        fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
        fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
    }

This function computes offsets in a loop. The offsets produce circular motion, and the offsets will reach the beginning of the circle every 5 seconds (controlled by `fLoopDuration`). The function `glutGet(GLUT_ELAPSED_TIME)` retrieves the integer time in milliseconds since the application started. The `fmodf` function computes the floating-point modulus of the time. In lay terms, it takes the first parameter and returns the remainder of the division between that and the second parameter. Thus, it returns a value on the range [0, `fLoopDuration`), which is what we need to create a periodically repeating pattern.

The `cosf` and `sinf` functions compute the cosine and sine respectively. It is not important to know exactly how these functions work, but they effectively compute a circle of diameter 2. By multiplying by 0.5f, it shrinks the circle down to a circle with a diameter of 1.

Once the offsets are computed, the offsets have to be added to the vertex data. This is done with the `AdjustVertexData` function:

Example 3.2. Adjusting the Vertex Data

    void AdjustVertexData(float fXOffset, float fYOffset)
    {
        std::vector<float> fNewData(ARRAY_COUNT(vertexPositions));
        memcpy(&fNewData[0], vertexPositions, sizeof(vertexPositions));

        for(int iVertex = 0; iVertex < ARRAY_COUNT(vertexPositions); iVertex += 4)
        {
            fNewData[iVertex] += fXOffset;
            fNewData[iVertex + 1] += fYOffset;
        }

        glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertexPositions), &fNewData[0]);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

This function works by copying the vertex data into a std::vector, then applying the offset to the X and Y coordinates of each vertex. The last three lines are the OpenGL-relevant parts.

First, the buffer objects containing the positions is bound to the context. Then the new function `glBufferSubData` is called to transfer this data to the buffer object.

The difference between `glBufferData` and `glBufferSubData` is that the SubData function does not allocate memory. `glBufferData` specifically allocates memory of a certain size; `glBufferSubData` only transfers data to the already existing memory. Calling `glBufferData` on a buffer object that has already been allocated tells OpenGL to reallocate this memory, throwing away the previous data and allocating a fresh block of memory. Whereas calling `glBufferSubData` on a buffer object that has not yet had memory allocated by `glBufferData` is an error.

Think of `glBufferData` as a combination of `malloc` and `memcpy`, while glBufferSubData is just `memcpy`.

The `glBufferSubData` function can update only a portion of the buffer object's memory. The second parameter to the function is the byte offset into the buffer object to begin copying to, and the third parameter is the number of bytes to copy. The fourth parameter is our array of bytes to be copied into that location of the buffer object.

The last line of the function is simply unbinding the buffer object. It is not strictly necessary, but it is good form to clean up binds after making them.

Buffer Object Usage Hints. Every time we draw something, we are changing the buffer object's data. OpenGL has a way to tell it that you will be doing something like this, and it is the purpose of the last parameter of `glBufferData`. This tutorial changed the allocation of the buffer object slightly, replacing:

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);

with this:

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STREAM_DRAW);

`GL_STATIC_DRAW` tells OpenGL that you intend to only set the data in this buffer object once. `GL_STREAM_DRAW` tells OpenGL that you intend to set this data constantly, generally once per frame. These parameters do not mean anything with regard to the API; they are simply hints to the OpenGL implementation. Proper use of these hints can be crucial for getting good buffer object performance when making frequent changes. We will see more of these hints later.

The rendering function now has become this:

Example 3.3. Updating and Drawing the Vertex Data

    void display()
    {
        float fXOffset = 0.0f, fYOffset = 0.0f;
        ComputePositionOffsets(fXOffset, fYOffset);
        AdjustVertexData(fXOffset, fYOffset);

        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(theProgram);

        glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

        glDrawArrays(GL_TRIANGLES, 0, 3);

        glDisableVertexAttribArray(0);
        glUseProgram(0);

        glutSwapBuffers();
        glutPostRedisplay();
    }

The first three lines get the offset and set the vertex data. Everything but the last line is unchanged from the first tutorial. The last line of the function is there to tell FreeGLUT to constantly call `display`. Ordinarily, `display` would only be called when the window's size changes or when the window is uncovered. `glutPostRedisplay` causes FreeGLUT to call `display` again. Not immediately, but reasonably fast.

If you run the tutorial, you will see a smaller triangle (the size was reduced in this tutorial) that slides around in a circle.

Prev | Up | Next
Part II. Positioning | Home | A Better Way