]> git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/tjunction.c
fe180508ee3ee8ab3df9df58d04eb64b0875656b
[xonotic/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         // unused element of doubly linked list
59         edgePoint_t *chain;
60 } edgeLine_t;
61
62 typedef struct {
63         float length;
64         bspDrawVert_t   *dv[2];
65 } originalEdge_t;
66
67 originalEdge_t  *originalEdges = NULL;
68 int numOriginalEdges;
69 int allocatedOriginalEdges = 0;
70
71 edgeLine_t *edgeLines = NULL;
72 int numEdgeLines;
73 int allocatedEdgeLines = 0;
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                         AUTOEXPAND_BY_REALLOC( originalEdges, numOriginalEdges, allocatedOriginalEdges, 1024 );
157                         originalEdges[ numOriginalEdges ].dv[0] = (bspDrawVert_t *)v1;
158                         originalEdges[ numOriginalEdges ].dv[1] = (bspDrawVert_t *)v2;
159                         originalEdges[ numOriginalEdges ].length = d;
160                         numOriginalEdges++;
161                         return -1;
162                 }
163         }
164
165         for ( i = 0 ; i < numEdgeLines ; i++ ) {
166                 e = &edgeLines[i];
167
168                 d = DotProduct( v1, e->normal1 ) - e->dist1;
169                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
170                         continue;
171                 }
172                 d = DotProduct( v1, e->normal2 ) - e->dist2;
173                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
174                         continue;
175                 }
176
177                 d = DotProduct( v2, e->normal1 ) - e->dist1;
178                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
179                         continue;
180                 }
181                 d = DotProduct( v2, e->normal2 ) - e->dist2;
182                 if ( d < -POINT_ON_LINE_EPSILON || d > POINT_ON_LINE_EPSILON ) {
183                         continue;
184                 }
185
186                 // this is the edge
187                 InsertPointOnEdge( v1, e );
188                 InsertPointOnEdge( v2, e );
189                 return i;
190         }
191
192         // create a new edge
193         AUTOEXPAND_BY_REALLOC( edgeLines, numEdgeLines, allocatedEdgeLines, 1024 );
194
195         e = &edgeLines[ numEdgeLines ];
196         numEdgeLines++;
197
198         e->chain = safe_malloc( sizeof( edgePoint_t ) );
199         e->chain->next = e->chain->prev = e->chain;
200
201         VectorCopy( v1, e->origin );
202         VectorCopy( dir, e->dir );
203
204         MakeNormalVectors( e->dir, e->normal1, e->normal2 );
205         e->dist1 = DotProduct( e->origin, e->normal1 );
206         e->dist2 = DotProduct( e->origin, e->normal2 );
207
208         InsertPointOnEdge( v1, e );
209         InsertPointOnEdge( v2, e );
210
211         return numEdgeLines - 1;
212 }
213
214
215
216 /*
217    AddSurfaceEdges()
218    adds a surface's edges
219  */
220
221 void AddSurfaceEdges( mapDrawSurface_t *ds ){
222         int i;
223
224
225         for ( i = 0; i < ds->numVerts; i++ )
226         {
227                 /* save the edge number in the lightmap field so we don't need to look it up again */
228                 ds->verts[i].lightmap[ 0 ][ 0 ] =
229                         AddEdge( ds->verts[ i ].xyz, ds->verts[ ( i + 1 ) % ds->numVerts ].xyz, qfalse );
230         }
231 }
232
233
234
235 /*
236    ColinearEdge()
237    determines if an edge is colinear
238  */
239
240 qboolean ColinearEdge( vec3_t v1, vec3_t v2, vec3_t v3 ){
241         vec3_t midpoint, dir, offset, on;
242         float d;
243
244         VectorSubtract( v2, v1, midpoint );
245         VectorSubtract( v3, v1, dir );
246         d = VectorNormalize( dir, dir );
247         if ( d == 0 ) {
248                 return qfalse;  // degenerate
249         }
250
251         d = DotProduct( midpoint, dir );
252         VectorScale( dir, d, on );
253         VectorSubtract( midpoint, on, offset );
254         d = VectorLength( offset );
255
256         if ( d < 0.1 ) {
257                 return qtrue;
258         }
259
260         return qfalse;
261 }
262
263
264
265 /*
266    ====================
267    AddPatchEdges
268
269    Add colinear border edges, which will fix some classes of patch to
270    brush tjunctions
271    ====================
272  */
273 void AddPatchEdges( mapDrawSurface_t *ds ) {
274         int i;
275         float   *v1, *v2, *v3;
276
277         for ( i = 0 ; i < ds->patchWidth - 2; i += 2 ) {
278                 v1 = ds->verts[ i ].xyz;
279                 v2 = ds->verts[ i + 1 ].xyz;
280                 v3 = ds->verts[ i + 2 ].xyz;
281
282                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
283                 if ( ColinearEdge( v1, v2, v3 ) ) {
284                         AddEdge( v1, v3, qfalse );
285                 }
286
287                 v1 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i ].xyz;
288                 v2 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 1 ].xyz;
289                 v3 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 2 ].xyz;
290
291                 // if v2 is on the v1 to v3 line, add an edge from v1 to v3
292                 if ( ColinearEdge( v1, v2, v3 ) ) {
293                         AddEdge( v1, v3, qfalse );
294                 }
295         }
296
297         for ( i = 0 ; i < ds->patchHeight - 2 ; i += 2 ) {
298                 v1 = ds->verts[ i * ds->patchWidth ].xyz;
299                 v2 = ds->verts[ ( i + 1 ) * ds->patchWidth ].xyz;
300                 v3 = ds->verts[ ( i + 2 ) * ds->patchWidth ].xyz;
301
302                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
303                 if ( ColinearEdge( v1, v2, v3 ) ) {
304                         AddEdge( v1, v3, qfalse );
305                 }
306
307                 v1 = ds->verts[ ( ds->patchWidth - 1 ) + i * ds->patchWidth ].xyz;
308                 v2 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 1 ) * ds->patchWidth ].xyz;
309                 v3 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 2 ) * ds->patchWidth ].xyz;
310
311                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
312                 if ( ColinearEdge( v1, v2, v3 ) ) {
313                         AddEdge( v1, v3, qfalse );
314                 }
315         }
316
317
318 }
319
320
321 /*
322    ====================
323    FixSurfaceJunctions
324    ====================
325  */
326 #define MAX_SURFACE_VERTS   256
327 void FixSurfaceJunctions( mapDrawSurface_t *ds ) {
328         int i, j, k;
329         edgeLine_t  *e;
330         edgePoint_t *p;
331         int counts[MAX_SURFACE_VERTS];
332         int originals[MAX_SURFACE_VERTS];
333         bspDrawVert_t verts[MAX_SURFACE_VERTS], *v1, *v2;
334         int numVerts;
335         float start, end, frac, c;
336         vec3_t delta;
337
338         // zero the verts array, verts are tested to not be null in FindMetaVertex()
339         memset( verts, 0, sizeof( verts ) );
340
341         numVerts = 0;
342         for ( i = 0 ; i < ds->numVerts ; i++ )
343         {
344                 counts[i] = 0;
345
346                 // copy first vert
347                 if ( numVerts == MAX_SURFACE_VERTS ) {
348                         Error( "MAX_SURFACE_VERTS" );
349                 }
350                 verts[numVerts] = ds->verts[i];
351                 originals[numVerts] = i;
352                 numVerts++;
353
354                 // check to see if there are any t junctions before the next vert
355                 v1 = &ds->verts[i];
356                 v2 = &ds->verts[ ( i + 1 ) % ds->numVerts ];
357
358                 j = (int)ds->verts[i].lightmap[ 0 ][ 0 ];
359                 if ( j == -1 ) {
360                         continue;       // degenerate edge
361                 }
362                 e = &edgeLines[ j ];
363
364                 VectorSubtract( v1->xyz, e->origin, delta );
365                 start = DotProduct( delta, e->dir );
366
367                 VectorSubtract( v2->xyz, e->origin, delta );
368                 end = DotProduct( delta, e->dir );
369
370
371                 if ( start < end ) {
372                         p = e->chain->next;
373                 }
374                 else {
375                         p = e->chain->prev;
376                 }
377
378                 for ( ; p != e->chain ; ) {
379                         if ( start < end ) {
380                                 if ( p->intercept > end - ON_EPSILON ) {
381                                         break;
382                                 }
383                         }
384                         else {
385                                 if ( p->intercept < end + ON_EPSILON ) {
386                                         break;
387                                 }
388                         }
389
390                         if (
391                                 ( start < end && p->intercept > start + ON_EPSILON ) ||
392                                 ( start > end && p->intercept < start - ON_EPSILON ) ) {
393                                 // insert this point
394                                 if ( numVerts == MAX_SURFACE_VERTS ) {
395                                         Error( "MAX_SURFACE_VERTS" );
396                                 }
397
398                                 /* take the exact intercept point */
399                                 VectorCopy( p->xyz, verts[ numVerts ].xyz );
400
401                                 /* interpolate the texture coordinates */
402                                 frac = ( p->intercept - start ) / ( end - start );
403                                 for ( j = 0 ; j < 2 ; j++ ) {
404                                         verts[ numVerts ].st[j] = v1->st[j] +
405                                                                                           frac * ( v2->st[j] - v1->st[j] );
406                                 }
407
408                                 /* copy the normal (FIXME: what about nonplanar surfaces? */
409                                 VectorCopy( v1->normal, verts[ numVerts ].normal );
410
411                                 /* ydnar: interpolate the color */
412                                 for ( k = 0; k < MAX_LIGHTMAPS; k++ )
413                                 {
414                                         for ( j = 0; j < 4; j++ )
415                                         {
416                                                 c = (float) v1->color[ k ][ j ] + frac * ( (float) v2->color[ k ][ j ] - (float) v1->color[ k ][ j ] );
417                                                 verts[ numVerts ].color[ k ][ j ] = (byte) ( c < 255.0f ? c : 255 );
418                                         }
419                                 }
420
421                                 /* next... */
422                                 originals[ numVerts ] = i;
423                                 numVerts++;
424                                 counts[ i ]++;
425                         }
426
427                         if ( start < end ) {
428                                 p = p->next;
429                         }
430                         else {
431                                 p = p->prev;
432                         }
433                 }
434         }
435
436         c_addedVerts += numVerts - ds->numVerts;
437         c_totalVerts += numVerts;
438
439
440         // FIXME: check to see if the entire surface degenerated
441         // after snapping
442
443         // rotate the points so that the initial vertex is between
444         // two non-subdivided edges
445         for ( i = 0 ; i < numVerts ; i++ ) {
446                 if ( originals[ ( i + 1 ) % numVerts ] == originals[ i ] ) {
447                         continue;
448                 }
449                 j = ( i + numVerts - 1 ) % numVerts;
450                 k = ( i + numVerts - 2 ) % numVerts;
451                 if ( originals[ j ] == originals[ k ] ) {
452                         continue;
453                 }
454                 break;
455         }
456
457         if ( i == 0 ) {
458                 // fine the way it is
459                 c_natural++;
460
461                 ds->numVerts = numVerts;
462                 ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
463                 memcpy( ds->verts, verts, numVerts * sizeof( *ds->verts ) );
464
465                 return;
466         }
467         if ( i == numVerts ) {
468                 // create a vertex in the middle to start the fan
469                 c_cant++;
470
471 /*
472         memset ( &verts[numVerts], 0, sizeof( verts[numVerts] ) );
473         for ( i = 0 ; i < numVerts ; i++ ) {
474             for ( j = 0 ; j < 10 ; j++ ) {
475                 verts[numVerts].xyz[j] += verts[i].xyz[j];
476             }
477         }
478         for ( j = 0 ; j < 10 ; j++ ) {
479             verts[numVerts].xyz[j] /= numVerts;
480         }
481
482         i = numVerts;
483         numVerts++;
484  */
485         }
486         else {
487                 // just rotate the vertexes
488                 c_rotate++;
489
490         }
491
492         ds->numVerts = numVerts;
493         ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
494
495         for ( j = 0 ; j < ds->numVerts ; j++ ) {
496                 ds->verts[j] = verts[ ( j + i ) % ds->numVerts ];
497         }
498 }
499
500
501
502
503
504 /*
505    FixBrokenSurface() - ydnar
506    removes nearly coincident verts from a planar winding surface
507    returns qfalse if the surface is broken
508  */
509
510 extern void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out );
511
512 #define DEGENERATE_EPSILON  0.1
513
514 int c_broken = 0;
515
516 qboolean FixBrokenSurface( mapDrawSurface_t *ds ){
517         bspDrawVert_t   *dv1, *dv2, avg;
518         int i, j, k;
519         float dist;
520
521
522         /* dummy check */
523         if ( ds == NULL ) {
524                 return qfalse;
525         }
526         if ( ds->type != SURFACE_FACE ) {
527                 return qfalse;
528         }
529
530         /* check all verts */
531         for ( i = 0; i < ds->numVerts; i++ )
532         {
533                 /* get verts */
534                 dv1 = &ds->verts[ i ];
535                 dv2 = &ds->verts[ ( i + 1 ) % ds->numVerts ];
536
537                 /* degenerate edge? */
538                 VectorSubtract( dv1->xyz, dv2->xyz, avg.xyz );
539                 dist = VectorLength( avg.xyz );
540                 if ( dist < DEGENERATE_EPSILON ) {
541                         Sys_FPrintf( SYS_VRB, "WARNING: Degenerate T-junction edge found, fixing...\n" );
542
543                         /* create an average drawvert */
544                         /* ydnar 2002-01-26: added nearest-integer welding preference */
545                         SnapWeldVector( dv1->xyz, dv2->xyz, avg.xyz );
546                         VectorAdd( dv1->normal, dv2->normal, avg.normal );
547                         VectorNormalize( avg.normal, avg.normal );
548                         avg.st[ 0 ] = ( dv1->st[ 0 ] + dv2->st[ 0 ] ) * 0.5f;
549                         avg.st[ 1 ] = ( dv1->st[ 1 ] + dv2->st[ 1 ] ) * 0.5f;
550
551                         /* lightmap st/colors */
552                         for ( k = 0; k < MAX_LIGHTMAPS; k++ )
553                         {
554                                 avg.lightmap[ k ][ 0 ] = ( dv1->lightmap[ k ][ 0 ] + dv2->lightmap[ k ][ 0 ] ) * 0.5f;
555                                 avg.lightmap[ k ][ 1 ] = ( dv1->lightmap[ k ][ 1 ] + dv2->lightmap[ k ][ 1 ] ) * 0.5f;
556                                 for ( j = 0; j < 4; j++ )
557                                         avg.color[ k ][ j ] = (int) ( dv1->color[ k ][ j ] + dv2->color[ k ][ j ] ) >> 1;
558                         }
559
560                         /* ydnar: der... */
561                         memcpy( dv1, &avg, sizeof( avg ) );
562
563                         /* move the remaining verts */
564                         for ( k = i + 2; k < ds->numVerts; k++ )
565                         {
566                                 /* get verts */
567                                 dv1 = &ds->verts[ k ];
568                                 dv2 = &ds->verts[ k - 1 ];
569
570                                 /* copy */
571                                 memcpy( dv2, dv1, sizeof( bspDrawVert_t ) );
572                         }
573                         ds->numVerts--;
574
575                         /* after welding, we have to consider the same vertex again, as it now has a new neighbor dv2 */
576                         --i;
577
578                         /* should ds->numVerts have become 0, then i is now -1. In the next iteration, the loop will abort. */
579                 }
580         }
581
582         /* one last check and return */
583         return ds->numVerts >= 3;
584 }
585
586
587
588
589
590
591
592
593
594 /*
595    ================
596    EdgeCompare
597    ================
598  */
599 int EdgeCompare( const void *elem1, const void *elem2 ) {
600         float d1, d2;
601
602         d1 = ( (const originalEdge_t *)elem1 )->length;
603         d2 = ( (const originalEdge_t *)elem2 )->length;
604
605         if ( d1 < d2 ) {
606                 return -1;
607         }
608         if ( d1 > d2 ) {
609                 return 1;
610         }
611         return 0;
612 }
613
614
615
616 /*
617    FixTJunctions
618    call after the surface list has been pruned
619  */
620
621 void FixTJunctions( entity_t *ent ){
622         int i;
623         mapDrawSurface_t    *ds;
624         shaderInfo_t        *si;
625         int axialEdgeLines;
626         originalEdge_t      *e;
627         bspDrawVert_t   *dv;
628
629         /* meta mode has its own t-junction code (currently not as good as this code) */
630         //%     if( meta )
631         //%             return;
632
633         /* note it */
634         Sys_FPrintf( SYS_VRB, "--- FixTJunctions ---\n" );
635         numEdgeLines = 0;
636         numOriginalEdges = 0;
637
638         // add all the edges
639         // this actually creates axial edges, but it
640         // only creates originalEdge_t structures
641         // for non-axial edges
642         for ( i = ent->firstDrawSurf ; i < numMapDrawSurfs ; i++ )
643         {
644                 /* get surface and early out if possible */
645                 ds = &mapDrawSurfs[ i ];
646                 si = ds->shaderInfo;
647                 if ( ( si->compileFlags & C_NODRAW ) || si->autosprite || si->notjunc || ds->numVerts == 0 ) {
648                         continue;
649                 }
650
651                 /* ydnar: gs mods: handle the various types of surfaces */
652                 switch ( ds->type )
653                 {
654                 /* handle brush faces */
655                 case SURFACE_FACE:
656                         AddSurfaceEdges( ds );
657                         break;
658
659                 /* handle patches */
660                 case SURFACE_PATCH:
661                         AddPatchEdges( ds );
662                         break;
663
664                 /* fixme: make triangle surfaces t-junction */
665                 default:
666                         break;
667                 }
668         }
669
670         axialEdgeLines = numEdgeLines;
671
672         // sort the non-axial edges by length
673         qsort( originalEdges, numOriginalEdges, sizeof( originalEdges[0] ), EdgeCompare );
674
675         // add the non-axial edges, longest first
676         // this gives the most accurate edge description
677         for ( i = 0 ; i < numOriginalEdges ; i++ ) {
678                 e = &originalEdges[i];
679                 dv = e->dv[0]; // e might change during AddEdge
680                 dv->lightmap[ 0 ][ 0 ] = AddEdge( e->dv[ 0 ]->xyz, e->dv[ 1 ]->xyz, qtrue );
681         }
682
683         Sys_FPrintf( SYS_VRB, "%9d axial edge lines\n", axialEdgeLines );
684         Sys_FPrintf( SYS_VRB, "%9d non-axial edge lines\n", numEdgeLines - axialEdgeLines );
685         Sys_FPrintf( SYS_VRB, "%9d degenerate edges\n", c_degenerateEdges );
686
687         // insert any needed vertexes
688         for ( i = ent->firstDrawSurf; i < numMapDrawSurfs ; i++ )
689         {
690                 /* get surface and early out if possible */
691                 ds = &mapDrawSurfs[ i ];
692                 si = ds->shaderInfo;
693                 if ( ( si->compileFlags & C_NODRAW ) || si->autosprite || si->notjunc || ds->numVerts == 0 || ds->type != SURFACE_FACE ) {
694                         continue;
695                 }
696
697                 /* ydnar: gs mods: handle the various types of surfaces */
698                 switch ( ds->type )
699                 {
700                 /* handle brush faces */
701                 case SURFACE_FACE:
702                         FixSurfaceJunctions( ds );
703                         if ( FixBrokenSurface( ds ) == qfalse ) {
704                                 c_broken++;
705                                 ClearSurface( ds );
706                         }
707                         break;
708
709                 /* fixme: t-junction triangle models and patches */
710                 default:
711                         break;
712                 }
713         }
714
715         /* emit some statistics */
716         Sys_FPrintf( SYS_VRB, "%9d verts added for T-junctions\n", c_addedVerts );
717         Sys_FPrintf( SYS_VRB, "%9d total verts\n", c_totalVerts );
718         Sys_FPrintf( SYS_VRB, "%9d naturally ordered\n", c_natural );
719         Sys_FPrintf( SYS_VRB, "%9d rotated orders\n", c_rotate );
720         Sys_FPrintf( SYS_VRB, "%9d can't order\n", c_cant );
721         Sys_FPrintf( SYS_VRB, "%9d broken (degenerate) surfaces removed\n", c_broken );
722 }