mirror of
https://github.com/brl/mutter.git
synced 2024-11-25 09:30:45 -05:00
447 lines
20 KiB
Plaintext
447 lines
20 KiB
Plaintext
/*
|
|
*/
|
|
|
|
General Polygon Tesselation
|
|
---------------------------
|
|
|
|
This note describes a tesselator for polygons consisting of one or
|
|
more closed contours. It is backward-compatible with the current
|
|
OpenGL Utilities tesselator, and is intended to replace it. Here is
|
|
a summary of the major differences:
|
|
|
|
- input contours can be intersecting, self-intersecting, or degenerate.
|
|
|
|
- supports a choice of several winding rules for determining which parts
|
|
of the polygon are on the "interior". This makes it possible to do
|
|
CSG operations on polygons.
|
|
|
|
- boundary extraction: instead of tesselating the polygon, returns a
|
|
set of closed contours which separate the interior from the exterior.
|
|
|
|
- returns the output as a small number of triangle fans and strips,
|
|
rather than a list of independent triangles (when possible).
|
|
|
|
- output is available as an explicit mesh (a quad-edge structure),
|
|
in addition to the normal callback interface.
|
|
|
|
- the algorithm used is extremely robust.
|
|
|
|
|
|
The interface
|
|
-------------
|
|
|
|
The tesselator state is maintained in a "tesselator object".
|
|
These are allocated and destroyed using
|
|
|
|
GLUtesselator *gluNewTess( void );
|
|
void gluDeleteTess( GLUtesselator *tess );
|
|
|
|
Several tesselator objects may be used simultaneously.
|
|
|
|
Inputs
|
|
------
|
|
|
|
The input contours are specified with the following routines:
|
|
|
|
void gluTessBeginPolygon( GLUtesselator *tess );
|
|
void gluTessBeginContour( GLUtesselator *tess );
|
|
void gluTessVertex( GLUtesselator *tess, GLUcoord coords[3], void *data );
|
|
void gluTessEndContour( GLUtesselator *tess );
|
|
void gluTessEndPolygon( GLUtesselator *tess );
|
|
|
|
Within each BeginPolygon/EndPolygon pair, there can be zero or more
|
|
calls to BeginContour/EndContour. Within each contour, there are zero
|
|
or more calls to gluTessVertex(). The vertices specify a closed
|
|
contour (the last vertex of each contour is automatically linked to
|
|
the first).
|
|
|
|
"coords" give the coordinates of the vertex in 3-space. For useful
|
|
results, all vertices should lie in some plane, since the vertices
|
|
are projected onto a plane before tesselation. "data" is a pointer
|
|
to a user-defined vertex structure, which typically contains other
|
|
information such as color, texture coordinates, normal, etc. It is
|
|
used to refer to the vertex during rendering.
|
|
|
|
The library can be compiled in single- or double-precision; the type
|
|
GLUcoord represents either "float" or "double" accordingly. The GLU
|
|
version will be available in double-precision only. Compile with
|
|
GLU_TESS_API_FLOAT defined to get the single-precision version.
|
|
|
|
When EndPolygon is called, the tesselation algorithm determines
|
|
which regions are interior to the given contours, according to one
|
|
of several "winding rules" described below. The interior regions
|
|
are then tesselated, and the output is provided as callbacks.
|
|
|
|
|
|
Rendering Callbacks
|
|
-------------------
|
|
|
|
Callbacks are specified by the client using
|
|
|
|
void gluTessCallback( GLUtesselator *tess, GLenum which, void (*fn)());
|
|
|
|
If "fn" is NULL, any previously defined callback is discarded.
|
|
|
|
The callbacks used to provide output are: /* which == */
|
|
|
|
void begin( GLenum type ); /* GLU_TESS_BEGIN */
|
|
void edgeFlag( GLboolean flag ); /* GLU_TESS_EDGE_FLAG */
|
|
void vertex( void *data ); /* GLU_TESS_VERTEX */
|
|
void end( void ); /* GLU_TESS_END */
|
|
|
|
Any of the callbacks may be left undefined; if so, the corresponding
|
|
information will not be supplied during rendering.
|
|
|
|
The "begin" callback indicates the start of a primitive; type is one
|
|
of GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, or GL_TRIANGLES (but see the
|
|
notes on "boundary extraction" below).
|
|
|
|
It is followed by any number of "vertex" callbacks, which supply the
|
|
vertices in the same order as expected by the corresponding glBegin()
|
|
call. After the last vertex of a given primitive, there is a callback
|
|
to "end".
|
|
|
|
If the "edgeFlag" callback is provided, no triangle fans or strips
|
|
will be used. When edgeFlag is called, if "flag" is GL_TRUE then each
|
|
vertex which follows begins an edge which lies on the polygon boundary
|
|
(ie. an edge which separates an interior region from an exterior one).
|
|
If "flag" is GL_FALSE, each vertex which follows begins an edge which lies
|
|
in the polygon interior. "edgeFlag" will be called before the first
|
|
call to "vertex".
|
|
|
|
Other Callbacks
|
|
---------------
|
|
|
|
void mesh( GLUmesh *mesh ); /* GLU_TESS_MESH */
|
|
|
|
- Returns an explicit mesh, represented using the quad-edge structure
|
|
(Guibas/Stolfi '85). Other implementations of this interface might
|
|
use a different mesh structure, so this is available only only as an
|
|
SGI extension. When the mesh is no longer needed, it should be freed
|
|
using
|
|
|
|
void gluDeleteMesh( GLUmesh *mesh );
|
|
|
|
There is a brief description of this data structure in the include
|
|
file "mesh.h". For the full details, see L. Guibas and J. Stolfi,
|
|
Primitives for the manipulation of general subdivisions and the
|
|
computation of Voronoi diagrams, ACM Transactions on Graphics,
|
|
4(2):74-123, April 1985. For an introduction, see the course notes
|
|
for CS348a, "Mathematical Foundations of Computer Graphics",
|
|
available at the Stanford bookstore (and taught during the fall
|
|
quarter).
|
|
|
|
void error( GLenum errno ); /* GLU_TESS_ERROR */
|
|
|
|
- errno is one of GLU_TESS_MISSING_BEGIN_POLYGON,
|
|
GLU_TESS_MISSING_END_POLYGON,
|
|
GLU_TESS_MISSING_BEGIN_CONTOUR,
|
|
GLU_TESS_MISSING_END_CONTOUR,
|
|
GLU_TESS_COORD_TOO_LARGE,
|
|
GLU_TESS_NEED_COMBINE_CALLBACK
|
|
|
|
The first four are obvious. The interface recovers from these
|
|
errors by inserting the missing call(s).
|
|
|
|
GLU_TESS_COORD_TOO_LARGE says that some vertex coordinate exceeded
|
|
the predefined constant GLU_TESS_MAX_COORD in absolute value, and
|
|
that the value has been clamped. (Coordinate values must be small
|
|
enough so that two can be multiplied together without overflow.)
|
|
|
|
GLU_TESS_NEED_COMBINE_CALLBACK says that the algorithm detected an
|
|
intersection between two edges in the input data, and the "combine"
|
|
callback (below) was not provided. No output will be generated.
|
|
|
|
|
|
void combine( GLUcoord coords[3], void *data[4], /* GLU_TESS_COMBINE */
|
|
GLUcoord weight[4], void **outData );
|
|
|
|
- When the algorithm detects an intersection, or wishes to merge
|
|
features, it needs to create a new vertex. The vertex is defined
|
|
as a linear combination of up to 4 existing vertices, referenced
|
|
by data[0..3]. The coefficients of the linear combination are
|
|
given by weight[0..3]; these weights always sum to 1.0. All vertex
|
|
pointers are valid even when some of the weights are zero.
|
|
"coords" gives the location of the new vertex.
|
|
|
|
The user must allocate another vertex, interpolate parameters
|
|
using "data" and "weights", and return the new vertex pointer in
|
|
"outData". This handle is supplied during rendering callbacks.
|
|
For example, if the polygon lies in an arbitrary plane in 3-space,
|
|
and we associate a color with each vertex, the combine callback might
|
|
look like this:
|
|
|
|
void myCombine( GLUcoord coords[3], VERTEX *d[4],
|
|
GLUcoord w[4], VERTEX **dataOut )
|
|
{
|
|
VERTEX *new = new_vertex();
|
|
|
|
new->x = coords[0];
|
|
new->y = coords[1];
|
|
new->z = coords[2];
|
|
new->r = w[0]*d[0]->r + w[1]*d[1]->r + w[2]*d[2]->r + w[3]*d[3]->r;
|
|
new->g = w[0]*d[0]->g + w[1]*d[1]->g + w[2]*d[2]->g + w[3]*d[3]->g;
|
|
new->b = w[0]*d[0]->b + w[1]*d[1]->b + w[2]*d[2]->b + w[3]*d[3]->b;
|
|
new->a = w[0]*d[0]->a + w[1]*d[1]->a + w[2]*d[2]->a + w[3]*d[3]->a;
|
|
*dataOut = new;
|
|
}
|
|
|
|
If the algorithm detects an intersection, then the "combine" callback
|
|
must be defined, and must write a non-NULL pointer into "dataOut".
|
|
Otherwise the GLU_TESS_NEED_COMBINE_CALLBACK error occurs, and no
|
|
output is generated. This is the only error that can occur during
|
|
tesselation and rendering.
|
|
|
|
|
|
Control over Tesselation
|
|
------------------------
|
|
|
|
void gluTessProperty( GLUtesselator *tess, GLenum which, GLUcoord value );
|
|
|
|
Properties defined:
|
|
|
|
- GLU_TESS_WINDING_RULE. Possible values:
|
|
|
|
GLU_TESS_WINDING_ODD
|
|
GLU_TESS_WINDING_NONZERO
|
|
GLU_TESS_WINDING_POSITIVE
|
|
GLU_TESS_WINDING_NEGATIVE
|
|
GLU_TESS_WINDING_ABS_GEQ_TWO
|
|
|
|
The input contours parition the plane into regions. A winding
|
|
rule determines which of these regions are inside the polygon.
|
|
|
|
For a single contour C, the winding number of a point x is simply
|
|
the signed number of revolutions we make around x as we travel
|
|
once around C (where CCW is positive). When there are several
|
|
contours, the individual winding numbers are summed. This
|
|
procedure associates a signed integer value with each point x in
|
|
the plane. Note that the winding number is the same for all
|
|
points in a single region.
|
|
|
|
The winding rule classifies a region as "inside" if its winding
|
|
number belongs to the chosen category (odd, nonzero, positive,
|
|
negative, or absolute value of at least two). The current GLU
|
|
tesselator implements the "odd" rule. The "nonzero" rule is another
|
|
common way to define the interior. The other three rules are
|
|
useful for polygon CSG operations (see below).
|
|
|
|
- GLU_TESS_BOUNDARY_ONLY. Values: TRUE (non-zero) or FALSE (zero).
|
|
|
|
If TRUE, returns a set of closed contours which separate the
|
|
polygon interior and exterior (rather than a tesselation).
|
|
Exterior contours are oriented CCW with respect to the normal,
|
|
interior contours are oriented CW. The GLU_TESS_BEGIN callback
|
|
uses the type GL_LINE_LOOP for each contour.
|
|
|
|
- GLU_TESS_TOLERANCE. Value: a real number between 0.0 and 1.0.
|
|
|
|
This specifies a tolerance for merging features to reduce the size
|
|
of the output. For example, two vertices which are very close to
|
|
each other might be replaced by a single vertex. The tolerance
|
|
is multiplied by the largest coordinate magnitude of any input vertex;
|
|
this specifies the maximum distance that any feature can move as the
|
|
result of a single merge operation. If a single feature takes part
|
|
in several merge operations, the total distance moved could be larger.
|
|
|
|
Feature merging is completely optional; the tolerance is only a hint.
|
|
The implementation is free to merge in some cases and not in others,
|
|
or to never merge features at all. The default tolerance is zero.
|
|
|
|
The current implementation merges vertices only if they are exactly
|
|
coincident, regardless of the current tolerance. A vertex is
|
|
spliced into an edge only if the implementation is unable to
|
|
distinguish which side of the edge the vertex lies on.
|
|
Two edges are merged only when both endpoints are identical.
|
|
|
|
|
|
void gluTessNormal( GLUtesselator *tess,
|
|
GLUcoord x, GLUcoord y, GLUcoord z )
|
|
|
|
- Lets the user supply the polygon normal, if known. All input data
|
|
is projected into a plane perpendicular to the normal before
|
|
tesselation. All output triangles are oriented CCW with
|
|
respect to the normal (CW orientation can be obtained by
|
|
reversing the sign of the supplied normal). For example, if
|
|
you know that all polygons lie in the x-y plane, call
|
|
"gluTessNormal(tess, 0.0, 0.0, 1.0)" before rendering any polygons.
|
|
|
|
- If the supplied normal is (0,0,0) (the default value), the
|
|
normal is determined as follows. The direction of the normal,
|
|
up to its sign, is found by fitting a plane to the vertices,
|
|
without regard to how the vertices are connected. It is
|
|
expected that the input data lies approximately in plane;
|
|
otherwise projection perpendicular to the computed normal may
|
|
substantially change the geometry. The sign of the normal is
|
|
chosen so that the sum of the signed areas of all input contours
|
|
is non-negative (where a CCW contour has positive area).
|
|
|
|
- The supplied normal persists until it is changed by another
|
|
call to gluTessNormal.
|
|
|
|
|
|
Backward compatibility with the GLU tesselator
|
|
----------------------------------------------
|
|
|
|
The preferred interface is the one described above. The following
|
|
routines are obsolete, and are provided only for backward compatibility:
|
|
|
|
typedef GLUtesselator GLUtriangulatorObj; /* obsolete name */
|
|
|
|
void gluBeginPolygon( GLUtesselator *tess );
|
|
void gluNextContour( GLUtesselator *tess, GLenum type );
|
|
void gluEndPolygon( GLUtesselator *tess );
|
|
|
|
"type" is one of GLU_EXTERIOR, GLU_INTERIOR, GLU_CCW, GLU_CW, or
|
|
GLU_UNKNOWN. It is ignored by the current GLU tesselator.
|
|
|
|
GLU_BEGIN, GLU_VERTEX, GLU_END, GLU_ERROR, and GLU_EDGE_FLAG are defined
|
|
as synonyms for GLU_TESS_BEGIN, GLU_TESS_VERTEX, GLU_TESS_END,
|
|
GLU_TESS_ERROR, and GLU_TESS_EDGE_FLAG.
|
|
|
|
|
|
Polygon CSG operations
|
|
----------------------
|
|
|
|
The features of the tesselator make it easy to find the union, difference,
|
|
or intersection of several polygons.
|
|
|
|
First, assume that each polygon is defined so that the winding number
|
|
is 0 for each exterior region, and 1 for each interior region. Under
|
|
this model, CCW contours define the outer boundary of the polygon, and
|
|
CW contours define holes. Contours may be nested, but a nested
|
|
contour must be oriented oppositely from the contour that contains it.
|
|
|
|
If the original polygons do not satisfy this description, they can be
|
|
converted to this form by first running the tesselator with the
|
|
GLU_TESS_BOUNDARY_ONLY property turned on. This returns a list of
|
|
contours satisfying the restriction above. By allocating two
|
|
tesselator objects, the callbacks from one tesselator can be fed
|
|
directly to the input of another.
|
|
|
|
Given two or more polygons of the form above, CSG operations can be
|
|
implemented as follows:
|
|
|
|
Union
|
|
Draw all the input contours as a single polygon. The winding number
|
|
of each resulting region is the number of original polygons
|
|
which cover it. The union can be extracted using the
|
|
GLU_TESS_WINDING_NONZERO or GLU_TESS_WINDING_POSITIVE winding rules.
|
|
Note that with the nonzero rule, we would get the same result if
|
|
all contour orientations were reversed.
|
|
|
|
Intersection (two polygons at a time only)
|
|
Draw a single polygon using the contours from both input polygons.
|
|
Extract the result using GLU_TESS_WINDING_ABS_GEQ_TWO. (Since this
|
|
winding rule looks at the absolute value, reversing all contour
|
|
orientations does not change the result.)
|
|
|
|
Difference
|
|
|
|
Suppose we want to compute A \ (B union C union D). Draw a single
|
|
polygon consisting of the unmodified contours from A, followed by
|
|
the contours of B,C,D with the vertex order reversed (this changes
|
|
the winding number of the interior regions to -1). To extract the
|
|
result, use the GLU_TESS_WINDING_POSITIVE rule.
|
|
|
|
If B,C,D are the result of a GLU_TESS_BOUNDARY_ONLY call, an
|
|
alternative to reversing the vertex order is to reverse the sign of
|
|
the supplied normal. For example in the x-y plane, call
|
|
gluTessNormal( tess, 0.0, 0.0, -1.0 ).
|
|
|
|
|
|
Performance
|
|
-----------
|
|
|
|
The tesselator is not intended for immediate-mode rendering; when
|
|
possible the output should be cached in a user structure or display
|
|
list. General polygon tesselation is an inherently difficult problem,
|
|
especially given the goal of extreme robustness.
|
|
|
|
The implementation makes an effort to output a small number of fans
|
|
and strips; this should improve the rendering performance when the
|
|
output is used in a display list.
|
|
|
|
Single-contour input polygons are first tested to see whether they can
|
|
be rendered as a triangle fan with respect to the first vertex (to
|
|
avoid running the full decomposition algorithm on convex polygons).
|
|
Non-convex polygons may be rendered by this "fast path" as well, if
|
|
the algorithm gets lucky in its choice of a starting vertex.
|
|
|
|
For best performance follow these guidelines:
|
|
|
|
- supply the polygon normal, if available, using gluTessNormal().
|
|
This represents about 10% of the computation time. For example,
|
|
if all polygons lie in the x-y plane, use gluTessNormal(tess,0,0,1).
|
|
|
|
- render many polygons using the same tesselator object, rather than
|
|
allocating a new tesselator for each one. (In a multi-threaded,
|
|
multi-processor environment you may get better performance using
|
|
several tesselators.)
|
|
|
|
|
|
Comparison with the GLU tesselator
|
|
----------------------------------
|
|
|
|
On polygons which make it through the "fast path", the tesselator is
|
|
3 to 5 times faster than the GLU tesselator.
|
|
|
|
On polygons which don't make it through the fast path (but which don't
|
|
have self-intersections or degeneracies), it is about 2 times slower.
|
|
|
|
On polygons with self-intersections or degeneraces, there is nothing
|
|
to compare against.
|
|
|
|
The new tesselator generates many more fans and strips, reducing the
|
|
number of vertices that need to be sent to the hardware.
|
|
|
|
Key to the statistics:
|
|
|
|
vert number of input vertices on all contours
|
|
cntr number of input contours
|
|
tri number of triangles in all output primitives
|
|
strip number of triangle strips
|
|
fan number of triangle fans
|
|
ind number of independent triangles
|
|
ms number of milliseconds for tesselation
|
|
(on a 150MHz R4400 Indy)
|
|
|
|
Convex polygon examples:
|
|
|
|
New: 3 vert, 1 cntr, 1 tri, 0 strip, 0 fan, 1 ind, 0.0459 ms
|
|
Old: 3 vert, 1 cntr, 1 tri, 0 strip, 0 fan, 1 ind, 0.149 ms
|
|
New: 4 vert, 1 cntr, 2 tri, 0 strip, 1 fan, 0 ind, 0.0459 ms
|
|
Old: 4 vert, 1 cntr, 2 tri, 0 strip, 0 fan, 2 ind, 0.161 ms
|
|
New: 36 vert, 1 cntr, 34 tri, 0 strip, 1 fan, 0 ind, 0.153 ms
|
|
Old: 36 vert, 1 cntr, 34 tri, 0 strip, 0 fan, 34 ind, 0.621 ms
|
|
|
|
Concave single-contour polygons:
|
|
|
|
New: 5 vert, 1 cntr, 3 tri, 0 strip, 1 fan, 0 ind, 0.052 ms
|
|
Old: 5 vert, 1 cntr, 3 tri, 0 strip, 0 fan, 3 ind, 0.252 ms
|
|
New: 19 vert, 1 cntr, 17 tri, 2 strip, 2 fan, 1 ind, 0.911 ms
|
|
Old: 19 vert, 1 cntr, 17 tri, 0 strip, 0 fan, 17 ind, 0.529 ms
|
|
New: 151 vert, 1 cntr, 149 tri, 13 strip, 18 fan, 3 ind, 6.82 ms
|
|
Old: 151 vert, 1 cntr, 149 tri, 0 strip, 3 fan, 143 ind, 2.7 ms
|
|
New: 574 vert, 1 cntr, 572 tri, 59 strip, 54 fan, 11 ind, 26.6 ms
|
|
Old: 574 vert, 1 cntr, 572 tri, 0 strip, 31 fan, 499 ind, 12.4 ms
|
|
|
|
Multiple contours, but no intersections:
|
|
|
|
New: 7 vert, 2 cntr, 7 tri, 1 strip, 0 fan, 0 ind, 0.527 ms
|
|
Old: 7 vert, 2 cntr, 7 tri, 0 strip, 0 fan, 7 ind, 0.274 ms
|
|
New: 81 vert, 6 cntr, 89 tri, 9 strip, 7 fan, 6 ind, 3.88 ms
|
|
Old: 81 vert, 6 cntr, 89 tri, 0 strip, 13 fan, 61 ind, 2.2 ms
|
|
New: 391 vert, 19 cntr, 413 tri, 37 strip, 32 fan, 26 ind, 20.2 ms
|
|
Old: 391 vert, 19 cntr, 413 tri, 0 strip, 25 fan, 363 ind, 8.68 ms
|
|
|
|
Self-intersecting and degenerate examples:
|
|
|
|
Bowtie: 4 vert, 1 cntr, 2 tri, 0 strip, 0 fan, 2 ind, 0.483 ms
|
|
Star: 5 vert, 1 cntr, 5 tri, 0 strip, 0 fan, 5 ind, 0.91 ms
|
|
Random: 24 vert, 7 cntr, 46 tri, 2 strip, 12 fan, 7 ind, 5.32 ms
|
|
Font: 333 vert, 2 cntr, 331 tri, 32 strip, 16 fan, 3 ind, 14.1 ms
|
|
: 167 vert, 35 cntr, 254 tri, 8 strip, 56 fan, 52 ind, 46.3 ms
|
|
: 78 vert, 1 cntr, 2675 tri, 148 strip, 207 fan, 180 ind, 243 ms
|
|
: 12480 vert, 2 cntr, 12478 tri, 736 strip,1275 fan, 5 ind, 1010 ms
|