Other Primitives
Triangles are the preferred primitive for object composition because most OpenGL hardware specifically accelerates triangles, but they are not the only primitives available. Some hardware provides for acceleration of other shapes as well, and programmatically, using a general-purpose graphics primitive might be simpler. The remaining OpenGL primitives provide for rapid specification of a quadrilateral or quadrilateral strip, as well as a general-purpose polygon.
Four-Sided Polygons: Quads
If you add one more side to a triangle, you get a quadrilateral, or a four-sided figure. OpenGL's GL_QUADS primitive draws a four-sided polygon. In Figure 3.25, a quad is drawn from four vertices. Note also that these quads have clockwise winding. One important rule to bear in mind when you use quads is that all four corners of the quadrilateral must lie in a plane (no bent quads!).
Figure 3.25 An example of GL_QUADS.
Quad Strips
As you can for triangle strips, you can specify a strip of connected quadrilaterals with the GL_QUAD_STRIP primitive. Figure 3.26 shows the progression of a quad strip specified by six vertices. Note that these quad strips maintain a clockwise winding.
Figure 3.26 Progression of GL_QUAD_STRIP.
General Polygons
The final OpenGL primitive is the GL_POLYGON, which you can use to draw a polygon having any number of sides. Figure 3.27 shows a polygon consisting of five vertices. Polygons, like quads, must have all vertices on the same plane. An easy way around this rule is to substitute GL_TRIANGLE_FAN for GL_POLYGON!
Figure 3.27 Progression of GL_POLYGON.
What About Rectangles?
All 10 of the OpenGL primitives are used with glBegin/glEnd to draw general-purpose polygonal shapes. Although in Chapter 2, we used the function glRect as an easy and convenient mechanism for specifying 2D rectangles, henceforth we will resort to using GL_QUADS.
Filling Polygons, or Stippling Revisited
There are two methods for applying a pattern to solid polygons. The customary method is texture mapping, in which an image is mapped to the surface of a polygon, and this is covered in Chapter 8, "Texture Mapping: The Basics." Another way is to specify a stippling pattern, as we did for lines. A polygon stipple pattern is nothing more than a 32x32 monochrome bitmap that is used for the fill pattern.
To enable polygon stippling, call
glEnable(GL_POLYGON_STIPPLE);
and then call
glPolygonStipple(pBitmap);
pBitmap is a pointer to a data area containing the stipple pattern. Hereafter, all polygons are filled using the pattern specified by pBitmap (GLubyte *). This pattern is similar to that used by line stippling, except the buffer is large enough to hold a 32-by-32-bit pattern. Also, the bits are read with the most significant bit (MSB) first, which is just the opposite of line stipple patterns. Figure 3.28 shows a bit pattern for a campfire that we use for a stipple pattern.
Figure 3.28 Building a polygon stipple pattern.
Pixel Storage
As you will learn in Chapter 7, "Imaging with OpenGL," you can modify the way pixels for stipple patterns are interpreted by using the glPixelStore function. For now, however, we stick to the simple default polygon stippling.
To construct a mask to represent this pattern, we store one row at a time from the bottom up. Fortunately, unlike line stipple patterns, the data is, by default, interpreted just as it is stored, with the most significant bit read first. Each byte can then be read from left to right and stored in an array of GLubyte large enough to hold 32 rows of 4 bytes apiece.
Listing 3.9 shows the code used to store this pattern. Each row of the array represents a row from Figure 3.28. The first row in the array is the last row of the figure, and so on, up to the last row of the array and the first row of the figure.
Listing 3.9 Mask Definition for the Campfire in Figure 3.28
// Bitmap of campfire GLubyte fire[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x07, 0xf0, 0x0f, 0x00, 0x1f, 0xe0, 0x1f, 0x80, 0x1f, 0xc0, 0x0f, 0xc0, 0x3f, 0x80, 0x07, 0xe0, 0x7e, 0x00, 0x03, 0xf0, 0xff, 0x80, 0x03, 0xf5, 0xff, 0xe0, 0x07, 0xfd, 0xff, 0xf8, 0x1f, 0xfc, 0xff, 0xe8, 0xff, 0xe3, 0xbf, 0x70, 0xde, 0x80, 0xb7, 0x00, 0x71, 0x10, 0x4a, 0x80, 0x03, 0x10, 0x4e, 0x40, 0x02, 0x88, 0x8c, 0x20, 0x05, 0x05, 0x04, 0x40, 0x02, 0x82, 0x14, 0x40, 0x02, 0x40, 0x10, 0x80, 0x02, 0x64, 0x1a, 0x80, 0x00, 0x92, 0x29, 0x00, 0x00, 0xb0, 0x48, 0x00, 0x00, 0xc8, 0x90, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00 };
To make use of this stipple pattern, we must first enable polygon stippling and then specify this pattern as the stipple pattern. The PSTIPPLE sample program does this and then draws an octagon using the stipple pattern. Listing 3.10 shows the pertinent code, and Figure 3.29 shows the output from PSTIPPLE.
Figure 3.29 Output from the PSTIPPLE program.
Listing 3.10 Code from PSTIPPLE That Draws a Stippled Octagon
// This function does any needed initialization on the rendering // context. void SetupRC() { // Black background glClearColor(0.0f, 0.0f, 0.0f, 1.0f ); // Set drawing color to red glColor3f(1.0f, 0.0f, 0.0f); // Enable polygon stippling glEnable(GL_POLYGON_STIPPLE); // Specify a specific stipple pattern glPolygonStipple(fire); } // Called to draw scene void RenderScene(void) { // Clear the window glClear(GL_COLOR_BUFFER_BIT); ... ... // Begin the stop sign shape, // use a standard polygon for simplicity glBegin(GL_POLYGON); glVertex2f(-20.0f, 50.0f); glVertex2f(20.0f, 50.0f); glVertex2f(50.0f, 20.0f); glVertex2f(50.0f, -20.0f); glVertex2f(20.0f, -50.0f); glVertex2f(-20.0f, -50.0f); glVertex2f(-50.0f, -20.0f); glVertex2f(-50.0f, 20.0f); glEnd(); ... ... // Flush drawing commands glFlush(); }
Figure 3.30 shows the octagon rotated somewhat. Notice that the stipple pattern is still used, but the pattern is not rotated with the polygon. The stipple pattern is used only for simple polygon filling onscreen. If you need to map an image to a polygon so that it mimics the polygon's surface, you must use texture mapping (see Chapter 8).
Figure 3.30 PSTIPPLE output with the polygon rotated, showing that the stipple pattern is not rotated.
Polygon Construction Rules
When you are using many polygons to construct a complex surface, you need to remember two important rules.
The first rule is that all polygons must be planar. That is, all the vertices of the polygon must lie in a single plane, as illustrated in Figure 3.31. The polygon cannot twist or bend in space.
Figure 3.31 Planar versus nonplanar polygons.
Here is yet another good reason to use triangles. No triangle can ever be twisted so that all three points do not line up in a plane because mathematically it only takes exactly three points to define a plane. (If you can plot an invalid triangle, aside from winding it in the wrong direction, the Nobel Prize committee might be looking for you!)
The second rule of polygon construction is that the polygon's edges must not intersect, and the polygon must be convex. A polygon intersects itself if any two of its lines cross. Convex means that the polygon cannot have any indentions. A more rigorous test of a convex polygon is to draw some lines through it. If any given line enters and leaves the polygon more than once, the polygon is not convex. Figure 3.32 gives examples of good and bad polygons.
Figure 3.32 Some valid and invalid primitive polygons.
Why the Limitations on Polygons?
You might wonder why OpenGL places the restrictions on polygon construction. Handling polygons can become quite complex, and OpenGL's restrictions allow it to use very fast algorithms for rendering these polygons. We predict that you'll not find these restrictions burdensome and that you'll be able to build any shapes or objects you need using the existing primitives. Chapter 10, "Curves and Surfaces," discusses some techniques for breaking a complex shape into smaller triangles.
Subdivision and Edges
Even though OpenGL can draw only convex polygons, there's still a way to create a nonconvex polygon: by arranging two or more convex polygons together. For example, let's take a four-point star, as shown in Figure 3.33. This shape is obviously not convex and thus violates OpenGL's rules for simple polygon construction. However, the star on the right is composed of six separate triangles, which are legal polygons.
When the polygons are filled, you won't be able to see any edges and the figure will seem to be a single shape onscreen. However, if you use glPolygonMode to switch to an outline drawing, it is distracting to see all those little triangles making up some larger surface area.
OpenGL provides a special flag called an edge flag to address those distracting edges. By setting and clearing the edge flag as you specify a list of vertices, you inform OpenGL which line segments are considered border lines (lines that go around the border of your shape) and which ones are not (internal lines that shouldn't be visible). The glEdgeFlag function takes a single parameter that sets the edge flag to True or False. When the function is set to True, any vertices that follow mark the beginning of a boundary line segment. Listing 3.11 shows an example of this from the STAR sample program on the CD.
Figure 3.33 A nonconvex four-point star made up of six triangles.
Listing 3.11 Sample Usage of glEdgeFlag from the STAR Program
// Begin the triangles glBegin(GL_TRIANGLES); glEdgeFlag(bEdgeFlag); glVertex2f(-20.0f, 0.0f); glEdgeFlag(TRUE); glVertex2f(20.0f, 0.0f); glVertex2f(0.0f, 40.0f); glVertex2f(-20.0f,0.0f); glVertex2f(-60.0f,-20.0f); glEdgeFlag(bEdgeFlag); glVertex2f(-20.0f,-40.0f); glEdgeFlag(TRUE); glVertex2f(-20.0f,-40.0f); glVertex2f(0.0f, -80.0f); glEdgeFlag(bEdgeFlag); glVertex2f(20.0f, -40.0f); glEdgeFlag(TRUE); glVertex2f(20.0f, -40.0f); glVertex2f(60.0f, -20.0f); glEdgeFlag(bEdgeFlag); glVertex2f(20.0f, 0.0f); glEdgeFlag(TRUE); // Center square as two triangles glEdgeFlag(bEdgeFlag); glVertex2f(-20.0f, 0.0f); glVertex2f(-20.0f,-40.0f); glVertex2f(20.0f, 0.0f); glVertex2f(-20.0f,-40.0f); glVertex2f(20.0f, -40.0f); glVertex2f(20.0f, 0.0f); glEdgeFlag(TRUE); // Done drawing Triangles glEnd();
The Boolean variable bEdgeFlag is toggled on and off by a menu option to make the edges appear and disappear. If this flag is True, all edges are considered boundary edges and appear when the polygon mode is set to GL_LINES. In Figures 3.34a and 3.34b, you can see the output from STAR, showing the wireframe star with and without edges.
Figure 3.34a STAR program with edges enabled.
Figure 3.34b STAR program without edges enabled.