Home > Articles > Programming > Graphic Programming

  • Print
  • + Share This
This chapter is from the book

Describing Points, Lines, and Polygons

This section explains how to describe OpenGL geometric primitives. All geometric primitives are eventually described in terms of their vertices—coordinates that define the points themselves, the endpoints of line segments, or the corners of polygons. The next section discusses how these primitives are displayed and what control you have over their display.

What Are Points, Lines, and Polygons?

You probably have a fairly good idea of what a mathematician means by the terms point, line, and polygon. The OpenGL meanings are similar, but not quite the same.

One difference comes from the limitations of computer-based calculations. In any OpenGL implementation, floating-point calculations are of finite precision, and they have round-off errors. Consequently, the coordinates of OpenGL points, lines, and polygons suffer from the same problems.

A more important difference arises from the limitations of a raster graphics display. On such a display, the smallest displayable unit is a pixel, and although pixels might be less than 1/100 of an inch wide, they are still much larger than the mathematician’s concepts of infinitely small (for points) and infinitely thin (for lines). When OpenGL performs calculations, it assumes that points are represented as vectors of floating-point numbers. However, a point is typically (but not always) drawn as a single pixel, and many different points with slightly different coordinates could be drawn by OpenGL on the same pixel.

Points

A point is represented by a set of floating-point numbers called a vertex. All internal calculations are done as if vertices are three-dimensional. Vertices specified by the user as two-dimensional (that is, with only x- and y-coordinates) are assigned a z-coordinate equal to zero by OpenGL.

Advanced

OpenGL works in the homogeneous coordinates of three-dimensional projective geometry, so for internal calculations, all vertices are represented with four floating-point coordinates (x, y, z, w). If w is different from zero, these coordinates correspond to the Euclidean, three-dimensional point (x/w, y/w, z/w). You can specify the w-coordinate in OpenGL commands, but this is rarely done. If the w-coordinate isn’t specified, it is understood to be 1.0. (See Appendix C for more information about homogeneous coordinate systems.)

Lines

In OpenGL, the term line refers to a line segment, not the mathematician’s version that extends to infinity in both directions. There are easy ways to specify a connected series of line segments, or even a closed, connected series of segments (see Figure 2-2). In all cases, though, the lines constituting the connected series are specified in terms of the vertices at their endpoints.

Figure 2-2

Figure 2-2 Two Connected Series of Line Segments

Polygons

Polygons are the areas enclosed by single closed loops of line segments, where the line segments are specified by the vertices at their endpoints. Polygons are typically drawn with the pixels in the interior filled in, but you can also draw them as outlines or a set of points. (See “Polygon Details” on page 60.)

In general, polygons can be complicated, so OpenGL imposes some strong restrictions on what constitutes a primitive polygon. First, the edges of OpenGL polygons can’t intersect (a mathematician would call a polygon satisfying this condition a simple polygon). Second, OpenGL polygons must be convex, meaning that they cannot have indentations. Stated precisely, a region is convex if, given any two points in the interior, the line segment joining them is also in the interior. See Figure 2-3 for some examples of valid and invalid polygons. OpenGL, however, doesn’t restrict the number of line segments making up the boundary of a convex polygon. Note that polygons with holes can’t be described. They are nonconvex, and they can’t be drawn with a boundary made up of a single closed loop. Be aware that if you present OpenGL with a nonconvex filled polygon, it might not draw it as you expect. For instance, on most systems, no more than the convex hull of the polygon would be filled. On some systems, less than the convex hull might be filled.

Figure 2-3

Figure 2-3 Valid and Invalid Polygons

The reason for the OpenGL restrictions on valid polygon types is that it’s simpler to provide fast polygon-rendering hardware for that restricted class of polygons. Simple polygons can be rendered quickly. The difficult cases are hard to detect quickly, so for maximum performance, OpenGL crosses its fingers and assumes the polygons are simple.

Many real-world surfaces consist of nonsimple polygons, nonconvex polygons, or polygons with holes. Since all such polygons can be formed from unions of simple convex polygons, some routines to build more complex objects are provided in the GLU library. These routines take complex descriptions and tessellate them, or break them down into groups of the simpler OpenGL polygons that can then be rendered. (See “Polygon Tessellation” in Chapter 11 for more information about the tessellation routines.)

Since OpenGL vertices are always three-dimensional, the points forming the boundary of a particular polygon don’t necessarily lie on the same plane in space. (Of course, they do in many cases—if all the z-coordinates are zero, for example, or if the polygon is a triangle.) If a polygon’s vertices don’t lie in the same plane, then after various rotations in space, changes in the viewpoint, and projection onto the display screen, the points might no longer form a simple convex polygon. For example, imagine a four-point quadrilateral where the points are slightly out of plane, and look at it almost edge-on. You can get a nonsimple polygon that resembles a bow tie, as shown in Figure 2-4, which isn’t guaranteed to be rendered correctly. This situation isn’t all that unusual if you approximate curved surfaces by quadrilaterals made of points lying on the true surface. You can always avoid the problem by using triangles, as any three points always lie on a plane.

Figure 2-4

Figure 2-4 Nonplanar Polygon Transformed to Nonsimple Polygon

Rectangles

Since rectangles are so common in graphics applications, OpenGL provides a filled-rectangle drawing primitive, glRect*(). You can draw a rectangle as a polygon, as described in “OpenGL Geometric Drawing Primitives” on page 47, but your particular implementation of OpenGL might have optimized glRect*() for rectangles.

Note that although the rectangle begins with a particular orientation in three-dimensional space (in the xy-plane and parallel to the axes), you can change this by applying rotations or other transformations. (See Chapter 3 for information about how to do this.)

Curves and Curved Surfaces

Any smoothly curved line or surface can be approximated—to any arbitrary degree of accuracy—by short line segments or small polygonal regions. Thus, subdividing curved lines and surfaces sufficiently and then approximating them with straight line segments or flat polygons makes them appear curved (see Figure 2-5). If you’re skeptical that this really works, imagine subdividing until each line segment or polygon is so tiny that it’s smaller than a pixel on the screen.

Figure 2-5

Figure 2-5 Approximating Curves

Even though curves aren’t geometric primitives, OpenGL provides some direct support for subdividing and drawing them. (See Chapter 12 for information about how to draw curves and curved surfaces.)

Specifying Vertices

With OpenGL, every geometric object is ultimately described as an ordered set of vertices. You use the glVertex*() command to specify a vertex.

Example 2-2 provides some examples of using glVertex*().

Example 2-2. Legal Uses of glVertex*()

glVertex2s(2, 3);
glVertex3d(0.0, 0.0, 3.1415926535898);
glVertex4f(2.3, 1.0, -2.2, 2.0);

GLdouble dvect[3] = {5.0, 9.0, 1992.0};
glVertex3dv(dvect);

The first example represents a vertex with three-dimensional coordinates (2, 3, 0). (Remember that if it isn’t specified, the z-coordinate is understood to be 0.) The coordinates in the second example are (0.0, 0.0, 3.1415926535898) (double-precision floating-point numbers). The third example represents the vertex with three-dimensional coordinates (1.15, 0.5, −1.1) as a homogenous coordinate. (Remember that the x-, y-, and z-coordinates are eventually divided by the w-coordinate.) In the final example, dvect is a pointer to an array of three double-precision floating-point numbers.

On some machines, the vector form of glVertex*() is more efficient, since only a single parameter needs to be passed to the graphics subsystem. Special hardware might be able to send a whole series of coordinates in a single batch. If your machine is like this, it’s to your advantage to arrange your data so that the vertex coordinates are packed sequentially in memory. In this case, there may be some gain in performance by using the vertex array operations of OpenGL. (See “Vertex Arrays” on page 70.)

OpenGL Geometric Drawing Primitives

Now that you’ve seen how to specify vertices, you still need to know how to tell OpenGL to create a set of points, a line, or a polygon from those vertices. To do this, you bracket each set of vertices between a call to glBegin() and a call to glEnd(). The argument passed to glBegin() determines what sort of geometric primitive is constructed from the vertices. For instance, Example 2-3 specifies the vertices for the polygon shown in Figure 2-6.

Example 2-3. Filled Polygon

glBegin(GL_POLYGON);
   glVertex2f(0.0, 0.0);
   glVertex2f(0.0, 3.0);
   glVertex2f(4.0, 3.0);
   glVertex2f(6.0, 1.5);
   glVertex2f(4.0, 0.0);
glEnd();
Figure 2-6

Figure 2-6 Drawing a Polygon or a Set of Points

If you had used GL_POINTS instead of GL_POLYGON, the primitive would have been simply the five points shown in Figure 2-6. Table 2-2 in the following function summary for glBegin() lists the 10 possible arguments and the corresponding types of primitives.

Table 2-2. Geometric Primitive Names and Meanings

Value

Meaning

GL_POINTS

Individual points

GL_LINES

Pairs of vertices interpreted as individual line segments

GL_LINE_STRIP

Series of connected line segments

GL_LINE_LOOP

Same as above, with a segment added between last and first vertices

GL_TRIANGLES

Triples of vertices interpreted as triangles

GL_TRIANGLE_STRIP

Linked strip of triangles

GL_TRIANGLE_FAN

Linked fan of triangles

GL_QUADS

Quadruples of vertices interpreted as four-sided polygons

GL_QUAD_STRIP

Linked strip of quadrilaterals

GL_POLYGON

Boundary of a simple, convex polygon

Figure 2-7 shows examples of all the geometric primitives listed in Table 2-2, with descriptions of the pixels that are drawn for each of the objects. Note that in addition to points, several types of lines and polygons are defined. Obviously, you can find many ways to draw the same primitive. The method you choose depends on your vertex data.

Figure 2-7

Figure 2-7 Geometric Primitive Types

As you read the following descriptions, assume that n vertices (v0, v1, v2, ... , vn–1) are described between a glBegin() and glEnd() pair.

GL_POINTS

Draws a point at each of the n vertices.

GL_LINES

Draws a series of unconnected line segments. Segments are drawn between v0 and v1, between v2 and v3, and so on. If n is odd, the last segment is drawn between vn−3 and vn−2, and vn−1 is ignored.

GL_LINE_STRIP

Draws a line segment from v0 to v1, then from v1 to v2, and so on, finally drawing the segment from vn−2 to vn−1. Thus, a total of n − 1 line segments are drawn. Nothing is drawn unless n is larger than 1. There are no restrictions on the vertices describing a line strip (or a line loop); the lines can intersect arbitrarily.

GL_LINE_LOOP

Same as GL_LINE_STRIP, except that a final line segment is drawn from vn−1 to v0, completing a loop.

GL_TRIANGLES

Draws a series of triangles (three-sided polygons) using vertices v0, v1, v2, then v3, v4, v5, and so on. If n isn’t a multiple of 3, the final one or two vertices are ignored.

GL_TRIANGLE_STRIP

Draws a series of triangles (three-sided polygons) using vertices v0, v1, v2, then v2, v1, v3 (note the order), then v2, v3, v4, and so on. The ordering is to ensure that the triangles are all drawn with the same orientation so that the strip can correctly form part of a surface. Preserving the orientation is important for some operations, such as culling (see “Reversing and Culling Polygon Faces” on page 61). n must be at least 3 for anything to be drawn.

GL_TRIANGLE_FAN

Same as GL_TRIANGLE_STRIP, except that the vertices are v0, v1, v2, then v0, v2, v3, then v0, v3, v4, and so on (see Figure 2-7).

GL_QUADS

Draws a series of quadrilaterals (four-sided polygons) using vertices v0, v1, v2, v3, then v4, v5, v6, v7, and so on. If n isn’t a multiple of 4, the final one, two, or three vertices are ignored.

GL_QUAD_STRIP

Draws a series of quadrilaterals (four-sided polygons) beginning with v0, v1, v3, v2, then v2, v3, v5, v4, then v4, v5, v7, v6, and so on (see Figure 2-7). n must be at least 4 before anything is drawn. If n is odd, the final vertex is ignored.

GL_POLYGON

Draws a polygon using the points v0, ... , vn−1 as vertices. n must be at least 3, or nothing is drawn. In addition, the polygon specified must not intersect itself and must be convex. If the vertices don’t satisfy these conditions, the results are unpredictable.

Restrictions on Using glBegin() and glEnd()

The most important information about vertices is their coordinates, which are specified by the glVertex*() command. You can also supply additional vertex-specific data for each vertex—a color, a normal vector, texture coordinates, or any combination of these—using special commands. In addition, a few other commands are valid between a glBegin() and glEnd() pair. Table 2-3 contains a complete list of such valid commands.

Table 2-3. Valid Commands between glBegin() and glEnd()

Command

Purpose of Command

Reference

glVertex*()

set vertex coordinates

Chapter 2

glColor*()

set RGBA color

Chapter 4

glIndex*()

set color index

Chapter 4

glSecondaryColor*()

set secondary color for post-texturing application

Chapter 9

glNormal*()

set normal vector coordinates

Chapter 2

glMaterial*()

set material properties

Chapter 5

glFogCoord*()

set fog coordinates

Chapter 6

glTexCoord*()

set texture coordinates

Chapter 9

glMultiTexCoord*()

set texture coordinates for multitexturing

Chapter 9

glVertexAttrib*()

set generic vertex attribute

Chapter 15

glEdgeFlag*()

control drawing of edges

Chapter 2

glArrayElement()

extract vertex array data

Chapter 2

glEvalCoord*(), glEvalPoint*()

generate coordinates

Chapter 12

glCallList(), glCallLists()

execute display list(s)

Chapter 7

No other OpenGL commands are valid between a glBegin() and glEnd() pair, and making most other OpenGL calls generates an error. Some vertex array commands, such as glEnableClientState() and glVertexPointer(), when called between glBegin() and glEnd(), have undefined behavior but do not necessarily generate an error. (Also, routines related to OpenGL, such as glX*() routines, have undefined behavior between glBegin() and glEnd().) These cases should be avoided, and debugging them may be more difficult.

Note, however, that only OpenGL commands are restricted; you can certainly include other programming-language constructs (except for calls, such as the aforementioned glX*() routines). For instance, Example 2-4 draws an outlined circle.

Example 2-4. Other Constructs between glBegin() and glEnd()

#define PI 3.1415926535898
GLint circle_points = 100;
glBegin(GL_LINE_LOOP);
for (i = 0; i < circle_points; i++) {
   angle = 2*PI*i/circle_points;
   glVertex2f(cos(angle), sin(angle));
}
glEnd();

Unless they are being compiled into a display list, all glVertex*() commands should appear between a glBegin() and glEnd() combination. (If they appear elsewhere, they don’t accomplish anything.) If they appear in a display list, they are executed only if they appear between a glBegin() and a glEnd(). (See Chapter 7 for more information about display lists.)

Although many commands are allowed between glBegin() and glEnd(), vertices are generated only when a glVertex*() command is issued. At the moment glVertex*() is called, OpenGL assigns the resulting vertex the current color, texture coordinates, normal vector information, and so on. To see this, look at the following code sequence. The first point is drawn in red, and the second and third ones in blue, despite the extra color commands:

glBegin(GL_POINTS);
   glColor3f(0.0, 1.0, 0.0);                  /* green */
   glColor3f(1.0, 0.0, 0.0);                  /* red */
   glVertex(...);
   glColor3f(1.0, 1.0, 0.0);                  /* yellow */
   glColor3f(0.0, 0.0, 1.0);                  /* blue */
   glVertex(...);
   glVertex(...);
glEnd();

You can use any combination of the 24 versions of the glVertex*() command between glBegin() and glEnd(), although in real applications all the calls in any particular instance tend to be of the same form. If your vertex-data specification is consistent and repetitive (for example, glColor*, glVertex*, glColor*, glVertex*,...), you may enhance your program’s performance by using vertex arrays. (See “Vertex Arrays” on page 70.)

  • + Share This
  • 🔖 Save To Your Account