]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/tjunction.c
46390dd0662daac127fb825a1e37c91d504717b2
[xonotic/netradiant.git] / tools / quake3 / q3map2 / tjunction.c
1 /*
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 ----------------------------------------------------------------------------------
22
23 This code has been altered significantly from its original form, to support
24 several games based on the Quake III Arena engine, in the form of "Q3Map2."
25
26 ------------------------------------------------------------------------------- */
27
28
29
30 /* marker */
31 #define TJUNCTION_C
32
33
34
35 /* dependencies */
36 #include "q3map2.h"
37
38
39
40
41 typedef struct edgePoint_s {
42         float           intercept;
43         vec3_t          xyz;
44         struct edgePoint_s      *prev, *next;
45 } edgePoint_t;
46
47 typedef struct edgeLine_s {
48         vec3_t          normal1;
49         float           dist1;
50         
51         vec3_t          normal2;
52         float           dist2;
53         
54         vec3_t          origin;
55         vec3_t          dir;
56
57         edgePoint_t     chain;          // unused element of doubly linked list
58 } edgeLine_t;
59
60 typedef struct {
61         float           length;
62         bspDrawVert_t   *dv[2];
63 } originalEdge_t;
64
65 #define MAX_ORIGINAL_EDGES      0x10000
66 originalEdge_t  originalEdges[MAX_ORIGINAL_EDGES];
67 int                             numOriginalEdges;
68
69
70 #define MAX_EDGE_LINES          0x10000
71 edgeLine_t              edgeLines[MAX_EDGE_LINES];
72 int                             numEdgeLines;
73
74 int                             c_degenerateEdges;
75 int                             c_addedVerts;
76 int                             c_totalVerts;
77
78 int                             c_natural, c_rotate, c_cant;
79
80 // these should be whatever epsilon we actually expect,
81 // plus SNAP_INT_TO_FLOAT 
82 #define LINE_POSITION_EPSILON   0.25
83 #define POINT_ON_LINE_EPSILON   0.25
84
85 /*
86 ====================
87 InsertPointOnEdge
88 ====================
89 */
90 void InsertPointOnEdge( vec3_t v, edgeLine_t *e ) {
91         vec3_t          delta;
92         float           d;
93         edgePoint_t     *p, *scan;
94
95         VectorSubtract( v, e->origin, delta );
96         d = DotProduct( delta, e->dir );
97
98         p = safe_malloc( sizeof(edgePoint_t) );
99         p->intercept = d;
100         VectorCopy( v, p->xyz );
101
102         if ( e->chain.next == &e->chain ) {
103                 e->chain.next = e->chain.prev = p;
104                 p->next = p->prev = &e->chain;
105                 return;
106         }
107
108         scan = e->chain.next;
109         for ( ; scan != &e->chain ; scan = scan->next ) {
110                 d = p->intercept - scan->intercept;
111                 if ( d > -LINE_POSITION_EPSILON && d < LINE_POSITION_EPSILON ) {
112                         free( p );
113                         return;         // the point is already set
114                 }
115
116                 if ( p->intercept < scan->intercept ) {
117                         // insert here
118                         p->prev = scan->prev;
119                         p->next = scan;
120                         scan->prev->next = p;
121                         scan->prev = p;
122                         return;
123                 }
124         }
125
126         // add at the end
127         p->prev = scan->prev;
128         p->next = scan;
129         scan->prev->next = p;
130         scan->prev = p;
131 }
132
133
134 /*
135 ====================
136 AddEdge
137 ====================
138 */
139 int AddEdge( vec3_t v1, vec3_t v2, qboolean createNonAxial ) {
140         int                     i;
141         edgeLine_t      *e;
142         float           d;
143         vec3_t          dir;
144
145         VectorSubtract( v2, v1, dir );
146         d = VectorNormalize( dir, dir );
147         if ( d < 0.1 ) {
148                 // if we added a 0 length vector, it would make degenerate planes
149                 c_degenerateEdges++;
150                 return -1;
151         }
152
153         if ( !createNonAxial ) {
154                 if ( fabs( dir[0] + dir[1] + dir[2] ) != 1.0 ) {
155                         if ( numOriginalEdges == MAX_ORIGINAL_EDGES ) {
156                                 Error( "MAX_ORIGINAL_EDGES" );
157                         }
158                         originalEdges[ numOriginalEdges ].dv[0] = (bspDrawVert_t *)v1;
159                         originalEdges[ numOriginalEdges ].dv[1] = (bspDrawVert_t *)v2;
160                         originalEdges[ numOriginalEdges ].length = d;
161                         numOriginalEdges++;
162                         return -1;
163                 }
164         }
165
166         for ( i = 0 ; i < numEdgeLines ; i++ ) {
167                 e = &edgeLines[i];
168
169                 d = DotProduct( v1, e->normal1 ) - e->dist1;
170                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
171                         continue;
172                 }
173                 d = DotProduct( v1, e->normal2 ) - e->dist2;
174                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
175                         continue;
176                 }
177
178                 d = DotProduct( v2, e->normal1 ) - e->dist1;
179                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
180                         continue;
181                 }
182                 d = DotProduct( v2, e->normal2 ) - e->dist2;
183                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
184                         continue;
185                 }
186
187                 // this is the edge
188                 InsertPointOnEdge( v1, e );
189                 InsertPointOnEdge( v2, e );
190                 return i;
191         }
192
193         // create a new edge
194         if ( numEdgeLines >= MAX_EDGE_LINES ) {
195                 Error( "MAX_EDGE_LINES" );
196         }
197
198         e = &edgeLines[ numEdgeLines ];
199         numEdgeLines++;
200
201         e->chain.next = e->chain.prev = &e->chain;
202
203         VectorCopy( v1, e->origin );
204         VectorCopy( dir, e->dir );
205
206         MakeNormalVectors( e->dir, e->normal1, e->normal2 );
207         e->dist1 = DotProduct( e->origin, e->normal1 );
208         e->dist2 = DotProduct( e->origin, e->normal2 );
209
210         InsertPointOnEdge( v1, e );
211         InsertPointOnEdge( v2, e );
212
213         return numEdgeLines - 1;
214 }
215
216
217
218 /*
219 AddSurfaceEdges()
220 adds a surface's edges
221 */
222
223 void AddSurfaceEdges( mapDrawSurface_t *ds )
224 {
225         int             i;
226         
227
228         for( i = 0; i < ds->numVerts; i++ )
229         {
230                 /* save the edge number in the lightmap field so we don't need to look it up again */
231                 ds->verts[i].lightmap[ 0 ][ 0 ] = 
232                         AddEdge( ds->verts[ i ].xyz, ds->verts[ (i + 1) % ds->numVerts ].xyz, qfalse );
233         }
234 }
235
236
237
238 /*
239 ColinearEdge()
240 determines if an edge is colinear
241 */
242
243 qboolean ColinearEdge( vec3_t v1, vec3_t v2, vec3_t v3 )
244 {
245         vec3_t  midpoint, dir, offset, on;
246         float   d;
247
248         VectorSubtract( v2, v1, midpoint );
249         VectorSubtract( v3, v1, dir );
250         d = VectorNormalize( dir, dir );
251         if ( d == 0 ) {
252                 return qfalse;  // degenerate
253         }
254
255         d = DotProduct( midpoint, dir );
256         VectorScale( dir, d, on );
257         VectorSubtract( midpoint, on, offset );
258         d = VectorLength ( offset );
259
260         if ( d < 0.1 ) {
261                 return qtrue;
262         }
263
264         return qfalse;
265 }
266
267
268
269 /*
270 ====================
271 AddPatchEdges
272
273 Add colinear border edges, which will fix some classes of patch to
274 brush tjunctions
275 ====================
276 */
277 void AddPatchEdges( mapDrawSurface_t *ds ) {
278         int             i;
279         float   *v1, *v2, *v3;
280
281         for ( i = 0 ; i < ds->patchWidth - 2; i+=2 ) {
282                 v1 = ds->verts[ i ].xyz;
283                 v2 = ds->verts[ i + 1 ].xyz;
284                 v3 = ds->verts[ i + 2 ].xyz;
285
286                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
287                 if ( ColinearEdge( v1, v2, v3 ) ) {
288                         AddEdge( v1, v3, qfalse );
289                 }
290
291                 v1 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i ].xyz;
292                 v2 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 1 ].xyz;
293                 v3 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 2 ].xyz;
294
295                 // if v2 is on the v1 to v3 line, add an edge from v1 to v3
296                 if ( ColinearEdge( v1, v2, v3 ) ) {
297                         AddEdge( v1, v3, qfalse );
298                 }
299         }
300
301         for ( i = 0 ; i < ds->patchHeight - 2 ; i+=2 ) {
302                 v1 = ds->verts[ i * ds->patchWidth ].xyz;
303                 v2 = ds->verts[ ( i + 1 ) * ds->patchWidth ].xyz;
304                 v3 = ds->verts[ ( i + 2 ) * ds->patchWidth ].xyz;
305
306                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
307                 if ( ColinearEdge( v1, v2, v3 ) ) {
308                         AddEdge( v1, v3, qfalse );
309                 }
310
311                 v1 = ds->verts[ ( ds->patchWidth - 1 ) + i * ds->patchWidth ].xyz;
312                 v2 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 1 ) * ds->patchWidth ].xyz;
313                 v3 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 2 ) * ds->patchWidth ].xyz;
314
315                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
316                 if ( ColinearEdge( v1, v2, v3 ) ) {
317                         AddEdge( v1, v3, qfalse );
318                 }
319         }
320
321
322 }
323
324
325 /*
326 ====================
327 FixSurfaceJunctions
328 ====================
329 */
330 #define MAX_SURFACE_VERTS       256
331 void FixSurfaceJunctions( mapDrawSurface_t *ds ) {
332         int                     i, j, k;
333         edgeLine_t      *e;
334         edgePoint_t     *p;
335         int                     originalVerts;
336         int                     counts[MAX_SURFACE_VERTS];
337         int                     originals[MAX_SURFACE_VERTS];
338         int                     firstVert[MAX_SURFACE_VERTS];
339         bspDrawVert_t   verts[MAX_SURFACE_VERTS], *v1, *v2;
340         int                     numVerts;
341         float           start, end, frac, c;
342         vec3_t          delta;
343         
344         
345         originalVerts = ds->numVerts;
346         
347         numVerts = 0;
348         for ( i = 0 ; i < ds->numVerts ; i++ )
349         {
350                 counts[i] = 0;
351                 firstVert[i] = numVerts;
352
353                 // copy first vert
354                 if ( numVerts == MAX_SURFACE_VERTS ) {
355                         Error( "MAX_SURFACE_VERTS" );
356                 }
357                 verts[numVerts] = ds->verts[i];
358                 originals[numVerts] = i;
359                 numVerts++;
360
361                 // check to see if there are any t junctions before the next vert
362                 v1 = &ds->verts[i];
363                 v2 = &ds->verts[ (i+1) % ds->numVerts ];
364
365                 j = (int)ds->verts[i].lightmap[ 0 ][ 0 ];
366                 if ( j == -1 ) {
367                         continue;               // degenerate edge
368                 }
369                 e = &edgeLines[ j ];
370                 
371                 VectorSubtract( v1->xyz, e->origin, delta );
372                 start = DotProduct( delta, e->dir );
373
374                 VectorSubtract( v2->xyz, e->origin, delta );
375                 end = DotProduct( delta, e->dir );
376
377
378                 if ( start < end ) {
379                         p = e->chain.next;
380                 } else {
381                         p = e->chain.prev;
382                 }
383
384                 for (  ; p != &e->chain ;  ) {
385                         if ( start < end ) {
386                                 if ( p->intercept > end - ON_EPSILON ) {
387                                         break;
388                                 }
389                         } else {
390                                 if ( p->intercept < end + ON_EPSILON ) {
391                                         break;
392                                 }
393                         }
394
395                         if ( 
396                                 ( start < end && p->intercept > start + ON_EPSILON ) ||
397                                 ( start > end && p->intercept < start - ON_EPSILON ) ) {
398                                 // insert this point
399                                 if ( numVerts == MAX_SURFACE_VERTS ) {
400                                         Error( "MAX_SURFACE_VERTS" );
401                                 }
402                                 
403                                 /* take the exact intercept point */
404                                 VectorCopy( p->xyz, verts[ numVerts ].xyz );
405                                 
406                                 /* interpolate the texture coordinates */
407                                 frac = ( p->intercept - start ) / ( end - start );
408                                 for ( j = 0 ; j < 2 ; j++ ) {
409                                         verts[ numVerts ].st[j] = v1->st[j] + 
410                                                 frac * ( v2->st[j] - v1->st[j] );
411                                 }
412                                 
413                                 /* copy the normal (FIXME: what about nonplanar surfaces? */
414                                 VectorCopy( v1->normal, verts[ numVerts ].normal );
415                                 
416                                 /* ydnar: interpolate the color */
417                                 for( k = 0; k < MAX_LIGHTMAPS; k++ )
418                                 {
419                                         for( j = 0; j < 4; j++ )
420                                         {
421                                                 c = (float) v1->color[ k ][ j ] + frac * ((float) v2->color[ k ][ j ] - (float) v1->color[ k ][ j ]);
422                                                 verts[ numVerts ].color[ k ][ j ] = (byte) (c < 255.0f ? c : 255);
423                                         }
424                                 }
425                                 
426                                 /* next... */
427                                 originals[ numVerts ] = i;
428                                 numVerts++;
429                                 counts[ i ]++;
430                         }
431
432                         if ( start < end ) {
433                                 p = p->next;
434                         } else {
435                                 p = p->prev;
436                         }
437                 }
438         }
439
440         c_addedVerts += numVerts - ds->numVerts;
441         c_totalVerts += numVerts;
442
443
444         // FIXME: check to see if the entire surface degenerated
445         // after snapping
446
447         // rotate the points so that the initial vertex is between
448         // two non-subdivided edges
449         for ( i = 0 ; i < numVerts ; i++ ) {
450                 if ( originals[ (i+1) % numVerts ] == originals[ i ] ) {
451                         continue;
452                 }
453                 j = (i + numVerts - 1 ) % numVerts;
454                 k = (i + numVerts - 2 ) % numVerts;
455                 if ( originals[ j ] == originals[ k ] ) {
456                         continue;
457                 }
458                 break;
459         }
460
461         if ( i == 0 ) {
462                 // fine the way it is
463                 c_natural++;
464
465                 ds->numVerts = numVerts;
466                 ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
467                 memcpy( ds->verts, verts, numVerts * sizeof( *ds->verts ) );
468
469                 return;
470         }
471         if ( i == numVerts ) {
472                 // create a vertex in the middle to start the fan
473                 c_cant++;
474
475 /*
476                 memset ( &verts[numVerts], 0, sizeof( verts[numVerts] ) );
477                 for ( i = 0 ; i < numVerts ; i++ ) {
478                         for ( j = 0 ; j < 10 ; j++ ) {
479                                 verts[numVerts].xyz[j] += verts[i].xyz[j];
480                         }
481                 }
482                 for ( j = 0 ; j < 10 ; j++ ) {
483                         verts[numVerts].xyz[j] /= numVerts;
484                 }
485
486                 i = numVerts;
487                 numVerts++;
488 */
489         } else {
490                 // just rotate the vertexes
491                 c_rotate++;
492
493         }
494
495         ds->numVerts = numVerts;
496         ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
497
498         for ( j = 0 ; j < ds->numVerts ; j++ ) {
499                 ds->verts[j] = verts[ ( j + i ) % ds->numVerts ];
500         }
501 }
502
503
504
505
506
507 /*
508 FixBrokenSurface() - ydnar
509 removes nearly coincident verts from a planar winding surface
510 returns qfalse if the surface is broken
511 */
512
513 extern void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out );
514
515 #define DEGENERATE_EPSILON      0.1
516
517 int             c_broken = 0;
518
519 qboolean FixBrokenSurface( mapDrawSurface_t *ds )
520 {
521         qboolean        valid = qtrue;
522         bspDrawVert_t   *dv1, *dv2, avg;
523         int                     i, j, k;
524         float           dist;
525         
526         
527         /* dummy check */
528         if( ds == NULL )
529                 return qfalse;
530         if( ds->type != SURFACE_FACE )
531                 return qfalse;
532         
533         /* check all verts */
534         for( i = 0; i < ds->numVerts; i++ )
535         {
536                 /* don't remove points if winding is a triangle */
537                 if( ds->numVerts == 3 )
538                         return valid;
539                 
540                 /* get verts */
541                 dv1 = &ds->verts[ i ];
542                 dv2 = &ds->verts[ (i + 1) % ds->numVerts ];
543                 
544                 /* degenerate edge? */
545                 VectorSubtract( dv1->xyz, dv2->xyz, avg.xyz );
546                 dist = VectorLength( avg.xyz );
547                 if( dist < DEGENERATE_EPSILON )
548                 {
549                         valid = qfalse;
550                         Sys_FPrintf( SYS_VRB, "WARNING: Degenerate T-junction edge found, fixing...\n" );
551
552                         /* create an average drawvert */
553                         /* ydnar 2002-01-26: added nearest-integer welding preference */
554                         SnapWeldVector( dv1->xyz, dv2->xyz, avg.xyz );
555                         VectorAdd( dv1->normal, dv2->normal, avg.normal );
556                         VectorNormalize( avg.normal, avg.normal );
557                         avg.st[ 0 ] = (dv1->st[ 0 ] + dv2->st[ 0 ]) * 0.5f;
558                         avg.st[ 1 ] = (dv1->st[ 1 ] + dv2->st[ 1 ]) * 0.5f;
559                         
560                         /* lightmap st/colors */
561                         for( k = 0; k < MAX_LIGHTMAPS; k++ )
562                         {
563                                 avg.lightmap[ k ][ 0 ] = (dv1->lightmap[ k ][ 0 ] + dv2->lightmap[ k ][ 0 ]) * 0.5f;
564                                 avg.lightmap[ k ][ 1 ] = (dv1->lightmap[ k ][ 1 ] + dv2->lightmap[ k ][ 1 ]) * 0.5f;
565                                 for( j = 0; j < 4; j++ )
566                                         avg.color[ k ][ j ] = (int) (dv1->color[ k ][ j ] + dv2->color[ k ][ j ]) >> 1;
567                         }
568                         
569                         /* ydnar: der... */
570                         memcpy( dv1, &avg, sizeof( avg ) );
571                         
572                         /* move the remaining verts */
573                         for( k = i + 2; k < ds->numVerts; k++ )
574                         {
575                                 /* get verts */
576                                 dv1 = &ds->verts[ k ];
577                                 dv2 = &ds->verts[ k - 1 ];
578                                 
579                                 /* copy */
580                                 memcpy( dv2, dv1, sizeof( bspDrawVert_t ) );
581                         }
582                         ds->numVerts--;
583                 }
584         }
585         
586         /* one last check and return */
587         if( ds->numVerts < 3 )
588                 valid = qfalse;
589         return valid;
590 }
591
592
593
594
595
596
597
598
599
600 /*
601 ================
602 EdgeCompare
603 ================
604 */
605 int EdgeCompare( const void *elem1, const void *elem2 ) {
606         float   d1, d2;
607
608         d1 = ((originalEdge_t *)elem1)->length;
609         d2 = ((originalEdge_t *)elem2)->length;
610
611         if ( d1 < d2 ) {
612                 return -1;
613         }
614         if ( d2 > d1 ) {
615                 return 1;
616         }
617         return 0;
618 }
619
620
621
622 /*
623 FixTJunctions
624 call after the surface list has been pruned
625 */
626
627 void FixTJunctions( entity_t *ent )
628 {
629         int                                     i;
630         mapDrawSurface_t        *ds;
631         shaderInfo_t            *si;
632         int                                     axialEdgeLines;
633         originalEdge_t          *e;
634         
635         
636         /* meta mode has its own t-junction code (currently not as good as this code) */
637         //%     if( meta )
638         //%             return; 
639         
640         /* note it */
641         Sys_FPrintf( SYS_VRB, "--- FixTJunctions ---\n" );
642         numEdgeLines = 0;
643         numOriginalEdges = 0;
644         
645         // add all the edges
646         // this actually creates axial edges, but it
647         // only creates originalEdge_t structures
648         // for non-axial edges
649         for ( i = ent->firstDrawSurf ; i < numMapDrawSurfs ; i++ )
650         {
651                 /* get surface and early out if possible */
652                 ds = &mapDrawSurfs[ i ];
653                 si = ds->shaderInfo;
654                 if( (si->compileFlags & C_NODRAW) || si->autosprite || si->notjunc || ds->numVerts == 0 )
655                         continue;
656                 
657                 /* ydnar: gs mods: handle the various types of surfaces */
658                 switch( ds->type )
659                 {
660                         /* handle brush faces */
661                         case SURFACE_FACE:
662                                 AddSurfaceEdges( ds );
663                                 break;
664                         
665                         /* handle patches */
666                         case SURFACE_PATCH:
667                                 AddPatchEdges( ds );
668                                 break;
669                         
670                         /* fixme: make triangle surfaces t-junction */
671                         default:
672                                 break;
673                 }
674         }
675
676         axialEdgeLines = numEdgeLines;
677
678         // sort the non-axial edges by length
679         qsort( originalEdges, numOriginalEdges, sizeof(originalEdges[0]), EdgeCompare );
680
681         // add the non-axial edges, longest first
682         // this gives the most accurate edge description
683         for ( i = 0 ; i < numOriginalEdges ; i++ ) {
684                 e = &originalEdges[i];
685                 e->dv[ 0 ]->lightmap[ 0 ][ 0 ] = AddEdge( e->dv[ 0 ]->xyz, e->dv[ 1 ]->xyz, qtrue );
686         }
687
688         Sys_FPrintf( SYS_VRB, "%9d axial edge lines\n", axialEdgeLines );
689         Sys_FPrintf( SYS_VRB, "%9d non-axial edge lines\n", numEdgeLines - axialEdgeLines );
690         Sys_FPrintf( SYS_VRB, "%9d degenerate edges\n", c_degenerateEdges );
691
692         // insert any needed vertexes
693         for( i = ent->firstDrawSurf; i < numMapDrawSurfs ; i++ )
694         {
695                 /* get surface and early out if possible */
696                 ds = &mapDrawSurfs[ i ];
697                 si = ds->shaderInfo;
698                 if( (si->compileFlags & C_NODRAW) || si->autosprite || si->notjunc || ds->numVerts == 0 || ds->type != SURFACE_FACE )
699                         continue;
700                 
701                 /* ydnar: gs mods: handle the various types of surfaces */
702                 switch( ds->type )
703                 {
704                         /* handle brush faces */
705                         case SURFACE_FACE:
706                                 FixSurfaceJunctions( ds );
707                                 if( FixBrokenSurface( ds ) == qfalse )
708                                 {
709                                         c_broken++;
710                                         ClearSurface( ds );
711                                 }
712                                 break;
713                         
714                         /* fixme: t-junction triangle models and patches */
715                         default:
716                                 break;
717                 }
718         }
719         
720         /* emit some statistics */
721         Sys_FPrintf( SYS_VRB, "%9d verts added for T-junctions\n", c_addedVerts );
722         Sys_FPrintf( SYS_VRB, "%9d total verts\n", c_totalVerts );
723         Sys_FPrintf( SYS_VRB, "%9d naturally ordered\n", c_natural );
724         Sys_FPrintf( SYS_VRB, "%9d rotated orders\n", c_rotate );
725         Sys_FPrintf( SYS_VRB, "%9d can't order\n", c_cant );
726         Sys_FPrintf( SYS_VRB, "%9d broken (degenerate) surfaces removed\n", c_broken );
727 }