Lesson 16: Advanced Primitives

You've learned about points, lines, triangles, quads and polygons, but is there still more? Of course there is! Line strips, line loops, triangle strips and fans, and quad strips await you, as well as a new rendering trick with glPolygonMode().

Theory

OpenGL's glBegin() and glEnd() are very powerful tools. Depending on the parameter you pass to glBegin(), the vertices of your great 3D models are interpreted in different ways. Here is the full list of available primitives that you can use between a glBegin()/glEnd() pair:

The many OpenGL graphics primitives.
Featured here are the different primitives that OpenGL can handle, and the order in which their vertices should be specified.

As you can see, the advanced primitives are extensions of the simple ones. But why have a line loop when you can just draw a series of lines? Why have a quad strip when you can just draw many quads?

Well, one of the many important rules of 3D rendering efficiency is this: the less information you send to the video card, the less time is wasted interpreting the data. If the data you pass is unnecessary, then it is a waste of valuable rendering time. This is why there are so many derived types. With a triangle fan made up of 12 triangles, the total amount of vertices passed to the video card is 14. The same fan made up of individual triangles requires 36 vertices to be passed to the video card. The importance of these types is plain when you're dealing with raw efficiency.

Implementation

Listing 16-1: agl16.cpp
#include <allegro.h>
#include <alleggl.h>
#include <vector>
#include <string>

// The font we will be using
FONT* Font;

// Possible primitives
const int NumPrimitives = 10;
const int Primitives[NumPrimitives] =
{
    GL_POINTS,
    GL_LINES,
    GL_LINE_STRIP,
    GL_LINE_LOOP,
    GL_TRIANGLES,
    GL_TRIANGLE_STRIP,
    GL_TRIANGLE_FAN,
    GL_QUADS,
    GL_QUAD_STRIP,
    GL_POLYGON
};

// Primitive names
string PrimitiveNames[NumPrimitives] =
{
    "GL_POINTS",
    "GL_LINES",
    "GL_LINE_STRIP",
    "GL_LINE_LOOP",
    "GL_TRIANGLES",
    "GL_TRIANGLE_STRIP",
    "GL_TRIANGLE_FAN",
    "GL_QUADS",
    "GL_QUAD_STRIP",
    "GL_POLYGON"
};

// A struct to hold a point.
struct Point
{
    float x, y;
    Point(float x, float y) : x(x), y(y) {}
};

void Init()
{
    // Start up Allegro and AllegroGL systems
    allegro_init();
    install_allegro_gl();
    install_keyboard();
    install_mouse();

    // Set some AllegroGL options
    allegro_gl_set(AGL_DOUBLEBUFFER, true);
    allegro_gl_set(AGL_COLOR_DEPTH, 16);
    allegro_gl_set(AGL_RENDERMETHOD, true);
    allegro_gl_set(AGL_SUGGEST, AGL_DOUBLEBUFFER | AGL_COLOR_DEPTH | AGL_RENDERMETHOD);

    // Set up a suitable viewing window
    set_color_depth(16);
    set_gfx_mode(GFX_OPENGL_WINDOWED, 640, 480, 0, 0);

    // Fill front-facing polygons and line back-facing ones.
    glPolygonMode(GL_FRONT, GL_FILL);
    glPolygonMode(GL_BACK, GL_LINE);

    // Set the background color to black
    glClearColor(0.0, 0.0, 0.0, 0.0);
}

int main()
{
    Init();

    std::vector<Point> vertices;
    bool mouse_b1 = false;
    bool upd_title = true;
    char* title_buf = 0;
    float mx = 0.0, my = 0.0;
    int prim = 0;

    while(!key[KEY_ESC])
    {
        // Clear the screen
        glClear(GL_COLOR_BUFFER_BIT);

        // Poll the mouse
        poll_mouse();
        mx = (mouse_x / 320.0) - 1.0;
        my = (-mouse_y / 240.0) + 1.0;

        // Check to see if a vertex needs to be added
        if(!(mouse_b & 1) && mouse_b1)  // Mouse button 1 up
        {
            vertices.push_back(Point(mx, my));
        }
        mouse_b1 = mouse_b & 1;

        // Check to see if the primitive should be drawn
        if((mouse_b & 2) && (vertices.size() > 0))
        {
            glBegin(Primitives[prim]);
                glColor3f(0.0, 0.0, 1.0);
                for(unsigned int i = 0; i < vertices.size(); i++)
                {
                    glVertex2f(vertices[i].x, vertices[i].y);
                }
            glEnd();
            glFlush();
            allegro_gl_flip();
            set_window_title("Press any key to continue.");
            upd_title = true;
            clear_keybuf();
            readkey();
        }

        // Clear the vertex list if necessary
        if(key[KEY_DEL] && (vertices.size() > 0))
        {
            vertices.clear();
        }

        // Draw the vertices and the mouse cursor
        glPointSize(5.0);
        glBegin(GL_POINTS);
            glColor3f(1.0, 0.0, 0.0);
            for(unsigned int i = 0; i < vertices.size(); i++)
            {
                glVertex2f(vertices[i].x, vertices[i].y);
            }
            glColor3f(0.8, 0.8, 0.8);
            glVertex2f(mx, my);
        glEnd();

        // Cycle through the available primitives.
        if(key[KEY_1])  { prim = 0; upd_title = true; }
        if(key[KEY_2])  { prim = 1; upd_title = true; }
        if(key[KEY_3])  { prim = 2; upd_title = true; }
        if(key[KEY_4])  { prim = 3; upd_title = true; }
        if(key[KEY_5])  { prim = 4; upd_title = true; }
        if(key[KEY_6])  { prim = 5; upd_title = true; }
        if(key[KEY_7])  { prim = 6; upd_title = true; }
        if(key[KEY_8])  { prim = 7; upd_title = true; }
        if(key[KEY_9])  { prim = 8; upd_title = true; }
        if(key[KEY_0])  { prim = 9; upd_title = true; }

        // Set the window title
        if(upd_title)
        {
            std::string title = string("Type: ") +
                PrimitiveNames[prim] +
                string(" | LMB: Add vtx | RMB: Draw prim. | 0-9: Change prim. type | DEL: Clear");
            delete[] title_buf;
            title_buf = new char[title.size()];
            strcpy(title_buf, title.c_str());
            set_window_title(title_buf);
            upd_title = false;
        }

        // Flush & flip.
        glFlush();
        allegro_gl_flip();
    }

    return 0;
}
END_OF_MAIN();

I hope I haven't made this example too long. Anyhow, the end result is pretty cool, if you ask me ;)

Use the mouse to move the cursor. Left-click to place a vertex. Right-click to draw a primitive combining all the placed vertices. Keys 0-9 change the type of primitive to be drawn. DEL clears the vertex list. Back-facing polygons are drawn outlined; front-facing ones are drawn filled.

An Analysis of agl16.cpp

Most of this program shouldn't be too hard to decipher. We make a few arrays in the style of the blending example and declare a simple Point struct. Everything should look familiar in the Init() function except for the two calls to glPolygonMode().

void glPolygonMode(int face, int mode)
Controls the way that polygons are rendered. face can be either GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK to determine if the rendering mode applies to front-facing polygons, back-facing ones, or both. mode controls how those polygons are rendered - GL_FILL is the default; polygons drawn like this are filled in. GL_LINE causes only the outlines of polygons to be drawn. GL_POINT causes only the corners of polygons to be shown. glLineWidth() and glPointSize() affect the rendering of polygons drawn in the GL_LINE and GL_POINT modes.

We use glPolygonMode() here in order to make back-facing polygons be drawn in outline, so that you can discover first-hand what order vertices should be specified in for the advanced primitives if they are to appear facing the camera

In the main function, we use a vector of vertices to hold the points that are ready to be rasterized. We draw the contents of the vertex vector in red dots, and when the right mouse button is clicked, the final result is drawn in blue.

In the Next Lesson...

In lesson 17, we will talk about display lists - a neat trick to speed up rendering time.