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