]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/tjunction.c
uncrustify! now the code is only ugly on the *inside*
[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         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  0x20000
67 originalEdge_t originalEdges[MAX_ORIGINAL_EDGES];
68 int numOriginalEdges;
69
70
71 #define MAX_EDGE_LINES      0x10000
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         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         vec3_t midpoint, dir, offset, on;
245         float d;
246
247         VectorSubtract( v2, v1, midpoint );
248         VectorSubtract( v3, v1, dir );
249         d = VectorNormalize( dir, dir );
250         if ( d == 0 ) {
251                 return qfalse;  // degenerate
252         }
253
254         d = DotProduct( midpoint, dir );
255         VectorScale( dir, d, on );
256         VectorSubtract( midpoint, on, offset );
257         d = VectorLength( offset );
258
259         if ( d < 0.1 ) {
260                 return qtrue;
261         }
262
263         return qfalse;
264 }
265
266
267
268 /*
269    ====================
270    AddPatchEdges
271
272    Add colinear border edges, which will fix some classes of patch to
273    brush tjunctions
274    ====================
275  */
276 void AddPatchEdges( mapDrawSurface_t *ds ) {
277         int i;
278         float   *v1, *v2, *v3;
279
280         for ( i = 0 ; i < ds->patchWidth - 2; i += 2 ) {
281                 v1 = ds->verts[ i ].xyz;
282                 v2 = ds->verts[ i + 1 ].xyz;
283                 v3 = ds->verts[ i + 2 ].xyz;
284
285                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
286                 if ( ColinearEdge( v1, v2, v3 ) ) {
287                         AddEdge( v1, v3, qfalse );
288                 }
289
290                 v1 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i ].xyz;
291                 v2 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 1 ].xyz;
292                 v3 = ds->verts[ ( ds->patchHeight - 1 ) * ds->patchWidth + i + 2 ].xyz;
293
294                 // if v2 is on the v1 to v3 line, add an edge from v1 to v3
295                 if ( ColinearEdge( v1, v2, v3 ) ) {
296                         AddEdge( v1, v3, qfalse );
297                 }
298         }
299
300         for ( i = 0 ; i < ds->patchHeight - 2 ; i += 2 ) {
301                 v1 = ds->verts[ i * ds->patchWidth ].xyz;
302                 v2 = ds->verts[ ( i + 1 ) * ds->patchWidth ].xyz;
303                 v3 = ds->verts[ ( i + 2 ) * ds->patchWidth ].xyz;
304
305                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
306                 if ( ColinearEdge( v1, v2, v3 ) ) {
307                         AddEdge( v1, v3, qfalse );
308                 }
309
310                 v1 = ds->verts[ ( ds->patchWidth - 1 ) + i * ds->patchWidth ].xyz;
311                 v2 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 1 ) * ds->patchWidth ].xyz;
312                 v3 = ds->verts[ ( ds->patchWidth - 1 ) + ( i + 2 ) * ds->patchWidth ].xyz;
313
314                 // if v2 is the midpoint of v1 to v3, add an edge from v1 to v3
315                 if ( ColinearEdge( v1, v2, v3 ) ) {
316                         AddEdge( v1, v3, qfalse );
317                 }
318         }
319
320
321 }
322
323
324 /*
325    ====================
326    FixSurfaceJunctions
327    ====================
328  */
329 #define MAX_SURFACE_VERTS   256
330 void FixSurfaceJunctions( mapDrawSurface_t *ds ) {
331         int i, j, k;
332         edgeLine_t  *e;
333         edgePoint_t *p;
334         int originalVerts;
335         int counts[MAX_SURFACE_VERTS];
336         int originals[MAX_SURFACE_VERTS];
337         int firstVert[MAX_SURFACE_VERTS];
338         bspDrawVert_t verts[MAX_SURFACE_VERTS], *v1, *v2;
339         int numVerts;
340         float start, end, frac, c;
341         vec3_t delta;
342
343
344         originalVerts = ds->numVerts;
345
346         numVerts = 0;
347         for ( i = 0 ; i < ds->numVerts ; i++ )
348         {
349                 counts[i] = 0;
350                 firstVert[i] = numVerts;
351
352                 // copy first vert
353                 if ( numVerts == MAX_SURFACE_VERTS ) {
354                         Error( "MAX_SURFACE_VERTS" );
355                 }
356                 verts[numVerts] = ds->verts[i];
357                 originals[numVerts] = i;
358                 numVerts++;
359
360                 // check to see if there are any t junctions before the next vert
361                 v1 = &ds->verts[i];
362                 v2 = &ds->verts[ ( i + 1 ) % ds->numVerts ];
363
364                 j = (int)ds->verts[i].lightmap[ 0 ][ 0 ];
365                 if ( j == -1 ) {
366                         continue;       // degenerate edge
367                 }
368                 e = &edgeLines[ j ];
369
370                 VectorSubtract( v1->xyz, e->origin, delta );
371                 start = DotProduct( delta, e->dir );
372
373                 VectorSubtract( v2->xyz, e->origin, delta );
374                 end = DotProduct( delta, e->dir );
375
376
377                 if ( start < end ) {
378                         p = e->chain.next;
379                 }
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                         }
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                         }
436                         else {
437                                 p = p->prev;
438                         }
439                 }
440         }
441
442         c_addedVerts += numVerts - ds->numVerts;
443         c_totalVerts += numVerts;
444
445
446         // FIXME: check to see if the entire surface degenerated
447         // after snapping
448
449         // rotate the points so that the initial vertex is between
450         // two non-subdivided edges
451         for ( i = 0 ; i < numVerts ; i++ ) {
452                 if ( originals[ ( i + 1 ) % numVerts ] == originals[ i ] ) {
453                         continue;
454                 }
455                 j = ( i + numVerts - 1 ) % numVerts;
456                 k = ( i + numVerts - 2 ) % numVerts;
457                 if ( originals[ j ] == originals[ k ] ) {
458                         continue;
459                 }
460                 break;
461         }
462
463         if ( i == 0 ) {
464                 // fine the way it is
465                 c_natural++;
466
467                 ds->numVerts = numVerts;
468                 ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
469                 memcpy( ds->verts, verts, numVerts * sizeof( *ds->verts ) );
470
471                 return;
472         }
473         if ( i == numVerts ) {
474                 // create a vertex in the middle to start the fan
475                 c_cant++;
476
477 /*
478         memset ( &verts[numVerts], 0, sizeof( verts[numVerts] ) );
479         for ( i = 0 ; i < numVerts ; i++ ) {
480             for ( j = 0 ; j < 10 ; j++ ) {
481                 verts[numVerts].xyz[j] += verts[i].xyz[j];
482             }
483         }
484         for ( j = 0 ; j < 10 ; j++ ) {
485             verts[numVerts].xyz[j] /= numVerts;
486         }
487
488         i = numVerts;
489         numVerts++;
490  */
491         }
492         else {
493                 // just rotate the vertexes
494                 c_rotate++;
495
496         }
497
498         ds->numVerts = numVerts;
499         ds->verts = safe_malloc( numVerts * sizeof( *ds->verts ) );
500
501         for ( j = 0 ; j < ds->numVerts ; j++ ) {
502                 ds->verts[j] = verts[ ( j + i ) % ds->numVerts ];
503         }
504 }
505
506
507
508
509
510 /*
511    FixBrokenSurface() - ydnar
512    removes nearly coincident verts from a planar winding surface
513    returns qfalse if the surface is broken
514  */
515
516 extern void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out );
517
518 #define DEGENERATE_EPSILON  0.1
519
520 int c_broken = 0;
521
522 qboolean FixBrokenSurface( mapDrawSurface_t *ds ){
523         qboolean valid = qtrue;
524         bspDrawVert_t   *dv1, *dv2, avg;
525         int i, j, k;
526         float dist;
527
528
529         /* dummy check */
530         if ( ds == NULL ) {
531                 return qfalse;
532         }
533         if ( ds->type != SURFACE_FACE ) {
534                 return qfalse;
535         }
536
537         /* check all verts */
538         for ( i = 0; i < ds->numVerts; i++ )
539         {
540                 /* don't remove points if winding is a triangle */
541                 if ( ds->numVerts == 3 ) {
542                         return valid;
543                 }
544
545                 /* get verts */
546                 dv1 = &ds->verts[ i ];
547                 dv2 = &ds->verts[ ( i + 1 ) % ds->numVerts ];
548
549                 /* degenerate edge? */
550                 VectorSubtract( dv1->xyz, dv2->xyz, avg.xyz );
551                 dist = VectorLength( avg.xyz );
552                 if ( dist < DEGENERATE_EPSILON ) {
553                         valid = qfalse;
554                         Sys_FPrintf( SYS_VRB, "WARNING: Degenerate T-junction edge found, fixing...\n" );
555
556                         /* create an average drawvert */
557                         /* ydnar 2002-01-26: added nearest-integer welding preference */
558                         SnapWeldVector( dv1->xyz, dv2->xyz, avg.xyz );
559                         VectorAdd( dv1->normal, dv2->normal, avg.normal );
560                         VectorNormalize( avg.normal, avg.normal );
561                         avg.st[ 0 ] = ( dv1->st[ 0 ] + dv2->st[ 0 ] ) * 0.5f;
562                         avg.st[ 1 ] = ( dv1->st[ 1 ] + dv2->st[ 1 ] ) * 0.5f;
563
564                         /* lightmap st/colors */
565                         for ( k = 0; k < MAX_LIGHTMAPS; k++ )
566                         {
567                                 avg.lightmap[ k ][ 0 ] = ( dv1->lightmap[ k ][ 0 ] + dv2->lightmap[ k ][ 0 ] ) * 0.5f;
568                                 avg.lightmap[ k ][ 1 ] = ( dv1->lightmap[ k ][ 1 ] + dv2->lightmap[ k ][ 1 ] ) * 0.5f;
569                                 for ( j = 0; j < 4; j++ )
570                                         avg.color[ k ][ j ] = (int) ( dv1->color[ k ][ j ] + dv2->color[ k ][ j ] ) >> 1;
571                         }
572
573                         /* ydnar: der... */
574                         memcpy( dv1, &avg, sizeof( avg ) );
575
576                         /* move the remaining verts */
577                         for ( k = i + 2; k < ds->numVerts; k++ )
578                         {
579                                 /* get verts */
580                                 dv1 = &ds->verts[ k ];
581                                 dv2 = &ds->verts[ k - 1 ];
582
583                                 /* copy */
584                                 memcpy( dv2, dv1, sizeof( bspDrawVert_t ) );
585                         }
586                         ds->numVerts--;
587                 }
588         }
589
590         /* one last check and return */
591         if ( ds->numVerts < 3 ) {
592                 valid = qfalse;
593         }
594         return valid;
595 }
596
597
598
599
600
601
602
603
604
605 /*
606    ================
607    EdgeCompare
608    ================
609  */
610 int EdgeCompare( const void *elem1, const void *elem2 ) {
611         float d1, d2;
612
613         d1 = ( (originalEdge_t *)elem1 )->length;
614         d2 = ( (originalEdge_t *)elem2 )->length;
615
616         if ( d1 < d2 ) {
617                 return -1;
618         }
619         if ( d2 > d1 ) {
620                 return 1;
621         }
622         return 0;
623 }
624
625
626
627 /*
628    FixTJunctions
629    call after the surface list has been pruned
630  */
631
632 void FixTJunctions( entity_t *ent ){
633         int i;
634         mapDrawSurface_t    *ds;
635         shaderInfo_t        *si;
636         int axialEdgeLines;
637         originalEdge_t      *e;
638
639
640         /* meta mode has its own t-junction code (currently not as good as this code) */
641         //%     if( meta )
642         //%             return;
643
644         /* note it */
645         Sys_FPrintf( SYS_VRB, "--- FixTJunctions ---\n" );
646         numEdgeLines = 0;
647         numOriginalEdges = 0;
648
649         // add all the edges
650         // this actually creates axial edges, but it
651         // only creates originalEdge_t structures
652         // for non-axial edges
653         for ( i = ent->firstDrawSurf ; i < numMapDrawSurfs ; i++ )
654         {
655                 /* get surface and early out if possible */
656                 ds = &mapDrawSurfs[ i ];
657                 si = ds->shaderInfo;
658                 if ( ( si->compileFlags & C_NODRAW ) || si->autosprite || si->notjunc || ds->numVerts == 0 ) {
659                         continue;
660                 }
661
662                 /* ydnar: gs mods: handle the various types of surfaces */
663                 switch ( ds->type )
664                 {
665                 /* handle brush faces */
666                 case SURFACE_FACE:
667                         AddSurfaceEdges( ds );
668                         break;
669
670                 /* handle patches */
671                 case SURFACE_PATCH:
672                         AddPatchEdges( ds );
673                         break;
674
675                 /* fixme: make triangle surfaces t-junction */
676                 default:
677                         break;
678                 }
679         }
680
681         axialEdgeLines = numEdgeLines;
682
683         // sort the non-axial edges by length
684         qsort( originalEdges, numOriginalEdges, sizeof( originalEdges[0] ), EdgeCompare );
685
686         // add the non-axial edges, longest first
687         // this gives the most accurate edge description
688         for ( i = 0 ; i < numOriginalEdges ; i++ ) {
689                 e = &originalEdges[i];
690                 e->dv[ 0 ]->lightmap[ 0 ][ 0 ] = AddEdge( e->dv[ 0 ]->xyz, e->dv[ 1 ]->xyz, qtrue );
691         }
692
693         Sys_FPrintf( SYS_VRB, "%9d axial edge lines\n", axialEdgeLines );
694         Sys_FPrintf( SYS_VRB, "%9d non-axial edge lines\n", numEdgeLines - axialEdgeLines );
695         Sys_FPrintf( SYS_VRB, "%9d degenerate edges\n", c_degenerateEdges );
696
697         // insert any needed vertexes
698         for ( i = ent->firstDrawSurf; i < numMapDrawSurfs ; i++ )
699         {
700                 /* get surface and early out if possible */
701                 ds = &mapDrawSurfs[ i ];
702                 si = ds->shaderInfo;
703                 if ( ( si->compileFlags & C_NODRAW ) || si->autosprite || si->notjunc || ds->numVerts == 0 || ds->type != SURFACE_FACE ) {
704                         continue;
705                 }
706
707                 /* ydnar: gs mods: handle the various types of surfaces */
708                 switch ( ds->type )
709                 {
710                 /* handle brush faces */
711                 case SURFACE_FACE:
712                         FixSurfaceJunctions( ds );
713                         if ( FixBrokenSurface( ds ) == qfalse ) {
714                                 c_broken++;
715                                 ClearSurface( ds );
716                         }
717                         break;
718
719                 /* fixme: t-junction triangle models and patches */
720                 default:
721                         break;
722                 }
723         }
724
725         /* emit some statistics */
726         Sys_FPrintf( SYS_VRB, "%9d verts added for T-junctions\n", c_addedVerts );
727         Sys_FPrintf( SYS_VRB, "%9d total verts\n", c_totalVerts );
728         Sys_FPrintf( SYS_VRB, "%9d naturally ordered\n", c_natural );
729         Sys_FPrintf( SYS_VRB, "%9d rotated orders\n", c_rotate );
730         Sys_FPrintf( SYS_VRB, "%9d can't order\n", c_cant );
731         Sys_FPrintf( SYS_VRB, "%9d broken (degenerate) surfaces removed\n", c_broken );
732 }