Polygon Tessellation (Tessellators and Quadrics) (OpenGL Programming) Part 1

A polygon is simple if the edges intersect only at vertices, there are no duplicate vertices, and exactly two edges meet at any vertex. If your application requires the display of concave polygons, polygons containing holes, or polygons with intersecting edges, these polygons must first be subdivided into simple convex polygons before they can be displayed. Such subdivision is called tessellation, and the GLU provides a collection of routines that perform tessellation. These routines take as input arbitrary contours, which describe hard-to-render polygons, and they return some combination of triangles, triangle meshes, triangle fans, and lines.

Figure 11-1 shows some contours of polygons that require tessellation: from left to right, a concave polygon, a polygon with a hole, and a self-intersecting polygon.

Contours That Require Tessellation

Figure 11-1 Contours That Require Tessellation

If you think a polygon may need tessellation, follow these typical steps:

1. Create a new tessellation object with gluNewTess().


2. Use gluTessCallback() several times to register callback functions to perform operations during the tessellation. The trickiest case for a callback function is when the tessellation algorithm detects an intersection and must call the function registered for the GLU_TESS_ COMBINE callback.

3. Specify tessellation properties by calling gluTessProperty(). The most important property is the winding rule, which determines the regions that should be filled and those that should remain unshaded.

4. Create and render tessellated polygons by specifying the contours of one or more closed polygons. If the data for the object is static, encapsulate the tessellated polygons in a display list. (If you don’t have to recalculate the tessellation repeatedly, using display lists is more efficient.)

5. If you need to tessellate something else, you may reuse your tessellation object. If you are forever finished with your tessellation object, you may delete it with gluDeIeteTess().

Note: The tessellator described here was introduced in Version 1.2 of the GLU.To query which version of GLU you have, use gluGetString(GLU_VERSION), which returns a string with your GLU version number. If you don’t seem to have gluGetStringO in your GLU, then you have GLU 1.0, which did not yet have the gluGetStringO routine.

Creating a Tessellation Object

As a complex polygon is being described and tessellated, it has associated data, such as the vertices, edges, and callback functions. All this data is tied to a single tessellation object.

A single tessellation object can be reused for all your tessellations. This object is required only because library routines might need to do their own tessellations, and they should be able to do so without interfering with any tessellation that your program is doing. It might also be useful to have multiple tessellation objects if you want to use different sets of callbacks for different tessellations. A typical program, however, allocates a single tessellation object and uses it for all its tessellations. There’s no real need to free it, because it uses a small amount of memory. On the other hand, it never hurts to be tidy.

Tessellation Callback Routines

After you create a tessellation object, you must provide a series of callback routines to be called at appropriate times during the tessellation. After specifying the callbacks, you describe the contours of one or more polygons using GLU routines. When the description of the contours is complete, the tessellation facility invokes your callback routines as necessary.

To change a callback routine, simply call gluTessCallback() with the new routine. To eliminate a callback routine without replacing it with a new one, pass gluTessCallback() a null pointer for the appropriate function.

As tessellation proceeds, the callback routines are called in a manner similar to how you use the OpenGL commands glBegin(), glEdgeFlag*(), glVertex*(), and glEnd().The combine callback is used to create new vertices where edges intersect. The error callback is invoked during the tessellation only if something goes wrong.

For every tessellator object created, a GLU_TESS_BEGIN callback is invoked with one of four possible parameters: GL_TRIANGLE_FAN, GL_TRIANGLE_ STRIP, GL_TRIANGLES, or GL_LINE_LOOP. When the tessellator decomposes the polygons, the tessellation algorithm decides which type of triangle primitive is most efficient to use. (If the GLU_TESS_BOUNDARY_ONLY property is enabled, then GL_LINE_LOOP is used for rendering.)

Since edge flags make no sense in a triangle fan or triangle strip, if there is a callback associated with GLU_TESS_EDGE_FLAG that enables edge flags, the GLU_TESS_BEGIN callback is called only with GL_TRIANGLES. The GLU_TESS_EDGE_FLAG callback works exactly analogously to the OpenGL glEdgeFlag*() call.

After the GLU_TESS_ BEGIN callback routine is called and before the callback associated with GLU_TESS_END is called, some combination of the GLU_TESS_EDGE_FLAG and GLU_TESS_VERTEX callbacks is invoked (usually by calls to gluTessVertex().The associated edge flags and vertices are interpreted exactly as they are in OpenGL between glBegin() and the matching glEnd().

If something goes wrong, the error callback is passed a GLU error number. A character string describing the error is obtained using the routine gluErrorString().

Example 11-1 shows a portion of tess.c, in which a tessellation object is created and several callbacks are registered.

Example 11-1 Registering Tessellation Callbacks: tess.c

Registering Tessellation Callbacks: tess.c

 

 

Registering Tessellation Callbacks: tess.c

Note: Type casting of callback functions is tricky especially if you wish to make code that runs equally well on Microsoft Windows and UNIX. To run on Microsoft Windows, programs that declare callback functions, such as tess.c, need the symbol CALLBACK in the declarations of functions. The trick of using an empty definition for CALLBACK (as demonstrated below) allows the code to run well on both Microsoft Windows and UNIX:

tmp1cfe209_thumb

In Example 11-1, the registered GLU_TESS_VERTEX callback is simply glVertex3dv(), and only the coordinates at each vertex are passed along. However, if you want to specify more information at every vertex, such as a color value, a surface normal vector, or a texture coordinate, you’ll have to make a more complex callback routine. Example 11-2 shows the start of another tessellated object, further along in program tess.c. The registered function vertexCallback() expects to receive a parameter that is a pointer to six double-length floating-point values: the χ-, γ-, and z-coordinates and the red, green, and blue color values for that vertex.

Example 11-2 Vertex and Combine Callbacks: tess.c

Vertex and Combine Callbacks: tess.c

Example 11-2 also shows the use of the GLU_TESS_COMBINE callback. Whenever the tessellation algorithm examines the input contours, detects an intersection, and decides it must create a new vertex, the GLU_TESS_ COMBINE callback is invoked. The callback is also called when the tessellator decides to merge features of two vertices that are very close to one another. The newly created vertex is a linear combination of up to four existing vertices, referenced by vertex_data[0..3] in Example 11-2. The coefficients of the linear combination are given by weight[0..3]; these weights sum to1.0. coords gives the location of the new vertex.

The registered callback routine must allocate memory for another vertex, perform a weighted interpolation of data using vertex_data and weight, and return the new vertex pointer as dataOut. combineCallback() in Example 11-2 interpolates the RGB color value. The function allocates a six-element array, puts the χ-, y-, and z-coordinates in the first three elements, and then puts the weighted average of the RGB color values in the last three elements.

User-Specified Data

Six kinds of callbacks can be registered. Since there are two versions of each kind of callback, there are 12 callbacks in all. For each kind of callback, there is one with user-specified data and one without. The user-specified data is given by the application to gluTessBeginPolygon() and is then passed, unaltered, to each *DATA callback routine. With GLU_TESS_BEGIN_DATA, the user-specified data may be used for "per-polygon" data. If you specify both versions of a particular callback, the callback with user_data is used, and the other is ignored. Therefore, although there are 12 callbacks, you can have a maximum of six callback functions active at any one time.

For instance, Example 11-2 uses smooth shading, so vertexCallbackO specifies an RGB color for every vertex. If you want to do lighting and smooth shading, the callback would specify a surface normal for every vertex. However, if you want lighting and flat shading, you might specify only one surface normal for every polygon, not for every vertex. In that case, you might choose to use the GLU_TESS_BEGIN_DATA callback and pass the vertex coordinates and surface normal in the user_data pointer.

Tessellation Properties

Prior to tessellation and rendering, you may use gluTessPropertyO to set several properties to affect the tessellation algorithm. The most important and complicated of these properties is the winding rule, which determines what is considered "interior" and "exterior.”

void gluTessProperty(GLUtesselator *tessobj, GLenum property,GLdouble value);

For the tessellation object tessobj, the current value of property is set to value, property is GLU_TESS_BOUNDARY_ONLY, GLU_TESS_TOLERANCE, or GLU_TESS_WINDING_RULE.

If property is GLU_FESS_BOUNDARY_ONLY, value is either GL_TRUE or GL_FALSE. When it is set to GL_TRLIE, polygons are no longer tessellated into filled polygons: line loops are drawn to outline the contours that separate the polygon interior and exterior. Fhe default value is GL_FALSE. (See gluTessNormal() to see how to control the winding direction of the contours.)

If property is GLU_TESS_TOLERANCE, value is a distance used to calculate whether two vertices are close enough together to be merged by the GLU_TESS_COMBINE callback. Fhe tolerance value is multiplied by the largest coordinate magnitude ot an input vertex to determine the maximum distance any feature can move as a result of a single merge operation. Feature merging may not be supported by your implementation, and the tolerance value is only a hint. The default tolerance value is zero.

The GLU_TESS_WINDING_RULE property determines which parts of the polygon are on the interior and which are on the exterior and should not be filled, value can be GLU_TESS_WINDING_ODD (the default), GLU_ FESS_WINDING_NONZERO, GLU_TESS_WINDING_POSITIVE, GLU_ TESS_WINDTNG_NEGATIVE, or GLU_TESS_WINDING_ABS_GEQ_TWO.

Next post:

Previous post: