1 /*
2 * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
3 * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice including the dates of first publication and
13 * either this permission notice or a reference to
14 * http://oss.sgi.com/projects/FreeB/
15 * shall be included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
22 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * Except as contained in this notice, the name of Silicon Graphics, Inc.
26 * shall not be used in advertising or otherwise to promote the sale, use or
27 * other dealings in this Software without prior written authorization from
28 * Silicon Graphics, Inc.
29 */
30 /*
31 ** Author: Eric Veach, July 1994.
32 **
33 */
34
35 #include <stddef.h>
36 #include <assert.h>
37 #include <setjmp.h>
38 #include "memalloc.h"
39 #include "tess.h"
40 #include "mesh.h"
41 #include "normal.h"
42 #include "sweep.h"
43 #include "tessmono.h"
44 #include "render.h"
45
46 #define GLU_TESS_DEFAULT_TOLERANCE 0.0
47 #define GLU_TESS_MESH 100112 /* void (*)(GLUmesh *mesh) */
48
49 #ifndef TRUE
50 #define TRUE 1
51 #endif
52 #ifndef FALSE
53 #define FALSE 0
54 #endif
55
noBegin(GLenum type)56 /*ARGSUSED*/ static void GLAPIENTRY noBegin( GLenum type ) {}
noEdgeFlag(GLboolean boundaryEdge)57 /*ARGSUSED*/ static void GLAPIENTRY noEdgeFlag( GLboolean boundaryEdge ) {}
noVertex(void * data)58 /*ARGSUSED*/ static void GLAPIENTRY noVertex( void *data ) {}
noEnd(void)59 /*ARGSUSED*/ static void GLAPIENTRY noEnd( void ) {}
noError(GLenum errnum)60 /*ARGSUSED*/ static void GLAPIENTRY noError( GLenum errnum ) {}
noCombine(GLdouble coords[3],void * data[4],GLfloat weight[4],void ** dataOut)61 /*ARGSUSED*/ static void GLAPIENTRY noCombine( GLdouble coords[3], void *data[4],
62 GLfloat weight[4], void **dataOut ) {}
noMesh(GLUmesh * mesh)63 /*ARGSUSED*/ static void GLAPIENTRY noMesh( GLUmesh *mesh ) {}
64
65
__gl_noBeginData(GLenum type,void * polygonData)66 /*ARGSUSED*/ void GLAPIENTRY __gl_noBeginData( GLenum type,
67 void *polygonData ) {}
__gl_noEdgeFlagData(GLboolean boundaryEdge,void * polygonData)68 /*ARGSUSED*/ void GLAPIENTRY __gl_noEdgeFlagData( GLboolean boundaryEdge,
69 void *polygonData ) {}
__gl_noVertexData(void * data,void * polygonData)70 /*ARGSUSED*/ void GLAPIENTRY __gl_noVertexData( void *data,
71 void *polygonData ) {}
__gl_noEndData(void * polygonData)72 /*ARGSUSED*/ void GLAPIENTRY __gl_noEndData( void *polygonData ) {}
__gl_noErrorData(GLenum errnum,void * polygonData)73 /*ARGSUSED*/ void GLAPIENTRY __gl_noErrorData( GLenum errnum,
74 void *polygonData ) {}
__gl_noCombineData(GLdouble coords[3],void * data[4],GLfloat weight[4],void ** outData,void * polygonData)75 /*ARGSUSED*/ void GLAPIENTRY __gl_noCombineData( GLdouble coords[3],
76 void *data[4],
77 GLfloat weight[4],
78 void **outData,
79 void *polygonData ) {}
80
81 /* Half-edges are allocated in pairs (see mesh.c) */
82 typedef struct { GLUhalfEdge e, eSym; } EdgePair;
83
84 #undef MAX
85 #define MAX(a,b) ((a) > (b) ? (a) : (b))
86 #define MAX_FAST_ALLOC (MAX(sizeof(EdgePair), \
87 MAX(sizeof(GLUvertex),sizeof(GLUface))))
88
89
90 GLUtesselator * GLAPIENTRY
gluNewTess(void)91 gluNewTess( void )
92 {
93 GLUtesselator *tess;
94
95 /* Only initialize fields which can be changed by the api. Other fields
96 * are initialized where they are used.
97 */
98
99 if (memInit( MAX_FAST_ALLOC ) == 0) {
100 return 0; /* out of memory */
101 }
102 tess = (GLUtesselator *)memAlloc( sizeof( GLUtesselator ));
103 if (tess == NULL) {
104 return 0; /* out of memory */
105 }
106
107 tess->state = T_DORMANT;
108
109 tess->normal[0] = 0;
110 tess->normal[1] = 0;
111 tess->normal[2] = 0;
112
113 tess->relTolerance = GLU_TESS_DEFAULT_TOLERANCE;
114 tess->windingRule = GLU_TESS_WINDING_ODD;
115 tess->flagBoundary = FALSE;
116 tess->boundaryOnly = FALSE;
117
118 tess->callBegin = &noBegin;
119 tess->callEdgeFlag = &noEdgeFlag;
120 tess->callVertex = &noVertex;
121 tess->callEnd = &noEnd;
122
123 tess->callError = &noError;
124 tess->callCombine = &noCombine;
125 tess->callMesh = &noMesh;
126
127 tess->callBeginData= &__gl_noBeginData;
128 tess->callEdgeFlagData= &__gl_noEdgeFlagData;
129 tess->callVertexData= &__gl_noVertexData;
130 tess->callEndData= &__gl_noEndData;
131 tess->callErrorData= &__gl_noErrorData;
132 tess->callCombineData= &__gl_noCombineData;
133
134 tess->polygonData= NULL;
135
136 return tess;
137 }
138
MakeDormant(GLUtesselator * tess)139 static void MakeDormant( GLUtesselator *tess )
140 {
141 /* Return the tessellator to its original dormant state. */
142
143 if( tess->mesh != NULL ) {
144 __gl_meshDeleteMesh( tess->mesh );
145 }
146 tess->state = T_DORMANT;
147 tess->lastEdge = NULL;
148 tess->mesh = NULL;
149 }
150
151 #define RequireState( tess, s ) if( tess->state != s ) GotoState(tess,s)
152
GotoState(GLUtesselator * tess,enum TessState newState)153 static void GotoState( GLUtesselator *tess, enum TessState newState )
154 {
155 while( tess->state != newState ) {
156 /* We change the current state one level at a time, to get to
157 * the desired state.
158 */
159 if( tess->state < newState ) {
160 switch( tess->state ) {
161 case T_DORMANT:
162 CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_POLYGON );
163 gluTessBeginPolygon( tess, NULL );
164 break;
165 case T_IN_POLYGON:
166 CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_CONTOUR );
167 gluTessBeginContour( tess );
168 break;
169 default:
170 ;
171 }
172 } else {
173 switch( tess->state ) {
174 case T_IN_CONTOUR:
175 CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_CONTOUR );
176 gluTessEndContour( tess );
177 break;
178 case T_IN_POLYGON:
179 CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_POLYGON );
180 /* gluTessEndPolygon( tess ) is too much work! */
181 MakeDormant( tess );
182 break;
183 default:
184 ;
185 }
186 }
187 }
188 }
189
190
191 void GLAPIENTRY
gluDeleteTess(GLUtesselator * tess)192 gluDeleteTess( GLUtesselator *tess )
193 {
194 RequireState( tess, T_DORMANT );
195 memFree( tess );
196 }
197
198
199 void GLAPIENTRY
gluTessProperty(GLUtesselator * tess,GLenum which,GLdouble value)200 gluTessProperty( GLUtesselator *tess, GLenum which, GLdouble value )
201 {
202 GLenum windingRule;
203
204 switch( which ) {
205 case GLU_TESS_TOLERANCE:
206 if( value < 0.0 || value > 1.0 ) break;
207 tess->relTolerance = value;
208 return;
209
210 case GLU_TESS_WINDING_RULE:
211 windingRule = (GLenum) value;
212 if( windingRule != value ) break; /* not an integer */
213
214 switch( windingRule ) {
215 case GLU_TESS_WINDING_ODD:
216 case GLU_TESS_WINDING_NONZERO:
217 case GLU_TESS_WINDING_POSITIVE:
218 case GLU_TESS_WINDING_NEGATIVE:
219 case GLU_TESS_WINDING_ABS_GEQ_TWO:
220 tess->windingRule = windingRule;
221 return;
222 default:
223 break;
224 }
225
226 case GLU_TESS_BOUNDARY_ONLY:
227 tess->boundaryOnly = (value != 0);
228 return;
229
230 default:
231 CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
232 return;
233 }
234 CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_VALUE );
235 }
236
237 /* Returns tessellator property */
238 void GLAPIENTRY
gluGetTessProperty(GLUtesselator * tess,GLenum which,GLdouble * value)239 gluGetTessProperty( GLUtesselator *tess, GLenum which, GLdouble *value )
240 {
241 switch (which) {
242 case GLU_TESS_TOLERANCE:
243 /* tolerance should be in range [0..1] */
244 assert(0.0 <= tess->relTolerance && tess->relTolerance <= 1.0);
245 *value= tess->relTolerance;
246 break;
247 case GLU_TESS_WINDING_RULE:
248 assert(tess->windingRule == GLU_TESS_WINDING_ODD ||
249 tess->windingRule == GLU_TESS_WINDING_NONZERO ||
250 tess->windingRule == GLU_TESS_WINDING_POSITIVE ||
251 tess->windingRule == GLU_TESS_WINDING_NEGATIVE ||
252 tess->windingRule == GLU_TESS_WINDING_ABS_GEQ_TWO);
253 *value= tess->windingRule;
254 break;
255 case GLU_TESS_BOUNDARY_ONLY:
256 assert(tess->boundaryOnly == TRUE || tess->boundaryOnly == FALSE);
257 *value= tess->boundaryOnly;
258 break;
259 default:
260 *value= 0.0;
261 CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
262 break;
263 }
264 } /* gluGetTessProperty() */
265
266 void GLAPIENTRY
gluTessNormal(GLUtesselator * tess,GLdouble x,GLdouble y,GLdouble z)267 gluTessNormal( GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z )
268 {
269 tess->normal[0] = x;
270 tess->normal[1] = y;
271 tess->normal[2] = z;
272 }
273
274 void GLAPIENTRY
gluTessCallback(GLUtesselator * tess,GLenum which,_GLUfuncptr fn)275 gluTessCallback( GLUtesselator *tess, GLenum which, _GLUfuncptr fn)
276 {
277 switch( which ) {
278 case GLU_TESS_BEGIN:
279 tess->callBegin = (fn == NULL) ? &noBegin : (void (GLAPIENTRY *)(GLenum)) fn;
280 return;
281 case GLU_TESS_BEGIN_DATA:
282 tess->callBeginData = (fn == NULL) ?
283 &__gl_noBeginData : (void (GLAPIENTRY *)(GLenum, void *)) fn;
284 return;
285 case GLU_TESS_EDGE_FLAG:
286 tess->callEdgeFlag = (fn == NULL) ? &noEdgeFlag :
287 (void (GLAPIENTRY *)(GLboolean)) fn;
288 /* If the client wants boundary edges to be flagged,
289 * we render everything as separate triangles (no strips or fans).
290 */
291 tess->flagBoundary = (fn != NULL);
292 return;
293 case GLU_TESS_EDGE_FLAG_DATA:
294 tess->callEdgeFlagData= (fn == NULL) ?
295 &__gl_noEdgeFlagData : (void (GLAPIENTRY *)(GLboolean, void *)) fn;
296 /* If the client wants boundary edges to be flagged,
297 * we render everything as separate triangles (no strips or fans).
298 */
299 tess->flagBoundary = (fn != NULL);
300 return;
301 case GLU_TESS_VERTEX:
302 tess->callVertex = (fn == NULL) ? &noVertex :
303 (void (GLAPIENTRY *)(void *)) fn;
304 return;
305 case GLU_TESS_VERTEX_DATA:
306 tess->callVertexData = (fn == NULL) ?
307 &__gl_noVertexData : (void (GLAPIENTRY *)(void *, void *)) fn;
308 return;
309 case GLU_TESS_END:
310 tess->callEnd = (fn == NULL) ? &noEnd : (void (GLAPIENTRY *)(void)) fn;
311 return;
312 case GLU_TESS_END_DATA:
313 tess->callEndData = (fn == NULL) ? &__gl_noEndData :
314 (void (GLAPIENTRY *)(void *)) fn;
315 return;
316 case GLU_TESS_ERROR:
317 tess->callError = (fn == NULL) ? &noError : (void (GLAPIENTRY *)(GLenum)) fn;
318 return;
319 case GLU_TESS_ERROR_DATA:
320 tess->callErrorData = (fn == NULL) ?
321 &__gl_noErrorData : (void (GLAPIENTRY *)(GLenum, void *)) fn;
322 return;
323 case GLU_TESS_COMBINE:
324 tess->callCombine = (fn == NULL) ? &noCombine :
325 (void (GLAPIENTRY *)(GLdouble [3],void *[4], GLfloat [4], void ** )) fn;
326 return;
327 case GLU_TESS_COMBINE_DATA:
328 tess->callCombineData = (fn == NULL) ? &__gl_noCombineData :
329 (void (GLAPIENTRY *)(GLdouble [3],
330 void *[4],
331 GLfloat [4],
332 void **,
333 void *)) fn;
334 return;
335 case GLU_TESS_MESH:
336 tess->callMesh = (fn == NULL) ? &noMesh : (void (GLAPIENTRY *)(GLUmesh *)) fn;
337 return;
338 default:
339 CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
340 return;
341 }
342 }
343
AddVertex(GLUtesselator * tess,GLdouble coords[3],void * data)344 static int AddVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
345 {
346 GLUhalfEdge *e;
347
348 e = tess->lastEdge;
349 if( e == NULL ) {
350 /* Make a self-loop (one vertex, one edge). */
351
352 e = __gl_meshMakeEdge( tess->mesh );
353 if (e == NULL) return 0;
354 if ( !__gl_meshSplice( e, e->Sym ) ) return 0;
355 } else {
356 /* Create a new vertex and edge which immediately follow e
357 * in the ordering around the left face.
358 */
359 if (__gl_meshSplitEdge( e ) == NULL) return 0;
360 e = e->Lnext;
361 }
362
363 /* The new vertex is now e->Org. */
364 e->Org->data = data;
365 e->Org->coords[0] = coords[0];
366 e->Org->coords[1] = coords[1];
367 e->Org->coords[2] = coords[2];
368
369 /* The winding of an edge says how the winding number changes as we
370 * cross from the edge''s right face to its left face. We add the
371 * vertices in such an order that a CCW contour will add +1 to
372 * the winding number of the region inside the contour.
373 */
374 e->winding = 1;
375 e->Sym->winding = -1;
376
377 tess->lastEdge = e;
378
379 return 1;
380 }
381
382
CacheVertex(GLUtesselator * tess,GLdouble coords[3],void * data)383 static void CacheVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
384 {
385 CachedVertex *v = &tess->cache[tess->cacheCount];
386
387 v->data = data;
388 v->coords[0] = coords[0];
389 v->coords[1] = coords[1];
390 v->coords[2] = coords[2];
391 ++tess->cacheCount;
392 }
393
394
EmptyCache(GLUtesselator * tess)395 static int EmptyCache( GLUtesselator *tess )
396 {
397 CachedVertex *v = tess->cache;
398 CachedVertex *vLast;
399
400 tess->mesh = __gl_meshNewMesh();
401 if (tess->mesh == NULL) return 0;
402
403 for( vLast = v + tess->cacheCount; v < vLast; ++v ) {
404 if ( !AddVertex( tess, v->coords, v->data ) ) return 0;
405 }
406 tess->cacheCount = 0;
407 tess->emptyCache = FALSE;
408
409 return 1;
410 }
411
412
413 void GLAPIENTRY
gluTessVertex(GLUtesselator * tess,GLdouble coords[3],void * data)414 gluTessVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
415 {
416 int i, tooLarge = FALSE;
417 GLdouble x, clamped[3];
418
419 RequireState( tess, T_IN_CONTOUR );
420
421 if( tess->emptyCache ) {
422 if ( !EmptyCache( tess ) ) {
423 CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
424 return;
425 }
426 tess->lastEdge = NULL;
427 }
428 for( i = 0; i < 3; ++i ) {
429 x = coords[i];
430 if( x < - GLU_TESS_MAX_COORD ) {
431 x = - GLU_TESS_MAX_COORD;
432 tooLarge = TRUE;
433 }
434 if( x > GLU_TESS_MAX_COORD ) {
435 x = GLU_TESS_MAX_COORD;
436 tooLarge = TRUE;
437 }
438 clamped[i] = x;
439 }
440 if( tooLarge ) {
441 CALL_ERROR_OR_ERROR_DATA( GLU_TESS_COORD_TOO_LARGE );
442 }
443
444 if( tess->mesh == NULL ) {
445 if( tess->cacheCount < TESS_MAX_CACHE ) {
446 CacheVertex( tess, clamped, data );
447 return;
448 }
449 if ( !EmptyCache( tess ) ) {
450 CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
451 return;
452 }
453 }
454 if ( !AddVertex( tess, clamped, data ) ) {
455 CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
456 }
457 }
458
459
460 void GLAPIENTRY
gluTessBeginPolygon(GLUtesselator * tess,void * data)461 gluTessBeginPolygon( GLUtesselator *tess, void *data )
462 {
463 RequireState( tess, T_DORMANT );
464
465 tess->state = T_IN_POLYGON;
466 tess->cacheCount = 0;
467 tess->emptyCache = FALSE;
468 tess->mesh = NULL;
469
470 tess->polygonData= data;
471 }
472
473
474 void GLAPIENTRY
gluTessBeginContour(GLUtesselator * tess)475 gluTessBeginContour( GLUtesselator *tess )
476 {
477 RequireState( tess, T_IN_POLYGON );
478
479 tess->state = T_IN_CONTOUR;
480 tess->lastEdge = NULL;
481 if( tess->cacheCount > 0 ) {
482 /* Just set a flag so we don't get confused by empty contours
483 * -- these can be generated accidentally with the obsolete
484 * NextContour() interface.
485 */
486 tess->emptyCache = TRUE;
487 }
488 }
489
490
491 void GLAPIENTRY
gluTessEndContour(GLUtesselator * tess)492 gluTessEndContour( GLUtesselator *tess )
493 {
494 RequireState( tess, T_IN_CONTOUR );
495 tess->state = T_IN_POLYGON;
496 }
497
498 void GLAPIENTRY
gluTessEndPolygon(GLUtesselator * tess)499 gluTessEndPolygon( GLUtesselator *tess )
500 {
501 GLUmesh *mesh;
502
503 if (setjmp(tess->env) != 0) {
504 /* come back here if out of memory */
505 CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
506 return;
507 }
508
509 RequireState( tess, T_IN_POLYGON );
510 tess->state = T_DORMANT;
511
512 if( tess->mesh == NULL ) {
513 if( ! tess->flagBoundary && tess->callMesh == &noMesh ) {
514
515 /* Try some special code to make the easy cases go quickly
516 * (eg. convex polygons). This code does NOT handle multiple contours,
517 * intersections, edge flags, and of course it does not generate
518 * an explicit mesh either.
519 */
520 if( __gl_renderCache( tess )) {
521 tess->polygonData= NULL;
522 return;
523 }
524 }
525 if ( !EmptyCache( tess ) ) longjmp(tess->env,1); /* could've used a label*/
526 }
527
528 /* Determine the polygon normal and project vertices onto the plane
529 * of the polygon.
530 */
531 __gl_projectPolygon( tess );
532
533 /* __gl_computeInterior( tess ) computes the planar arrangement specified
534 * by the given contours, and further subdivides this arrangement
535 * into regions. Each region is marked "inside" if it belongs
536 * to the polygon, according to the rule given by tess->windingRule.
537 * Each interior region is guaranteed be monotone.
538 */
539 if ( !__gl_computeInterior( tess ) ) {
540 longjmp(tess->env,1); /* could've used a label */
541 }
542
543 mesh = tess->mesh;
544 if( ! tess->fatalError ) {
545 int rc = 1;
546
547 /* If the user wants only the boundary contours, we throw away all edges
548 * except those which separate the interior from the exterior.
549 * Otherwise we tessellate all the regions marked "inside".
550 */
551 if( tess->boundaryOnly ) {
552 rc = __gl_meshSetWindingNumber( mesh, 1, TRUE );
553 } else {
554 rc = __gl_meshTessellateInterior( mesh );
555 }
556 if (rc == 0) longjmp(tess->env,1); /* could've used a label */
557
558 __gl_meshCheckMesh( mesh );
559
560 if( tess->callBegin != &noBegin || tess->callEnd != &noEnd
561 || tess->callVertex != &noVertex || tess->callEdgeFlag != &noEdgeFlag
562 || tess->callBeginData != &__gl_noBeginData
563 || tess->callEndData != &__gl_noEndData
564 || tess->callVertexData != &__gl_noVertexData
565 || tess->callEdgeFlagData != &__gl_noEdgeFlagData )
566 {
567 if( tess->boundaryOnly ) {
568 __gl_renderBoundary( tess, mesh ); /* output boundary contours */
569 } else {
570 __gl_renderMesh( tess, mesh ); /* output strips and fans */
571 }
572 }
573 if( tess->callMesh != &noMesh ) {
574
575 /* Throw away the exterior faces, so that all faces are interior.
576 * This way the user doesn't have to check the "inside" flag,
577 * and we don't need to even reveal its existence. It also leaves
578 * the freedom for an implementation to not generate the exterior
579 * faces in the first place.
580 */
581 __gl_meshDiscardExterior( mesh );
582 (*tess->callMesh)( mesh ); /* user wants the mesh itself */
583 tess->mesh = NULL;
584 tess->polygonData= NULL;
585 return;
586 }
587 }
588 __gl_meshDeleteMesh( mesh );
589 tess->polygonData= NULL;
590 tess->mesh = NULL;
591 }
592
593
594 /*XXXblythe unused function*/
595 #if 0
596 void GLAPIENTRY
597 gluDeleteMesh( GLUmesh *mesh )
598 {
599 __gl_meshDeleteMesh( mesh );
600 }
601 #endif
602
603
604
605 /*******************************************************/
606
607 /* Obsolete calls -- for backward compatibility */
608
609 void GLAPIENTRY
gluBeginPolygon(GLUtesselator * tess)610 gluBeginPolygon( GLUtesselator *tess )
611 {
612 gluTessBeginPolygon( tess, NULL );
613 gluTessBeginContour( tess );
614 }
615
616
617 /*ARGSUSED*/
618 void GLAPIENTRY
gluNextContour(GLUtesselator * tess,GLenum type)619 gluNextContour( GLUtesselator *tess, GLenum type )
620 {
621 gluTessEndContour( tess );
622 gluTessBeginContour( tess );
623 }
624
625
626 void GLAPIENTRY
gluEndPolygon(GLUtesselator * tess)627 gluEndPolygon( GLUtesselator *tess )
628 {
629 gluTessEndContour( tess );
630 gluTessEndPolygon( tess );
631 }
632