]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/map.c
Merge branch 'NateEag-master-patch-12920' into 'master'
[xonotic/netradiant.git] / tools / quake3 / q3map2 / map.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 MAP_C
33
34
35
36 /* dependencies */
37 #include "q3map2.h"
38
39
40
41 /* FIXME: remove these vars */
42
43 /* undefine to make plane finding use linear sort (note: really slow) */
44 #define USE_HASHING
45 #define PLANE_HASHES    8192
46
47 int planehash[ PLANE_HASHES ];
48
49 int c_boxbevels;
50 int c_edgebevels;
51 int c_areaportals;
52 int c_detail;
53 int c_structural;
54
55
56
57 /*
58    PlaneEqual()
59    ydnar: replaced with variable epsilon for djbob
60  */
61
62 qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ){
63         float ne, de;
64
65
66         /* get local copies */
67         ne = normalEpsilon;
68         de = distanceEpsilon;
69
70         /* compare */
71         // We check equality of each component since we're using '<', not '<='
72         // (the epsilons may be zero).  We want to use '<' instead of '<=' to be
73         // consistent with the true meaning of "epsilon", and also because other
74         // parts of the code uses this inequality.
75         if ( ( p->dist == dist || fabs( p->dist - dist ) < de ) &&
76                  ( p->normal[0] == normal[0] || fabs( p->normal[0] - normal[0] ) < ne ) &&
77                  ( p->normal[1] == normal[1] || fabs( p->normal[1] - normal[1] ) < ne ) &&
78                  ( p->normal[2] == normal[2] || fabs( p->normal[2] - normal[2] ) < ne ) ) {
79                 return qtrue;
80         }
81
82         /* different */
83         return qfalse;
84 }
85
86
87
88 /*
89    AddPlaneToHash()
90  */
91
92 void AddPlaneToHash( plane_t *p ){
93         int hash;
94
95
96         hash = ( PLANE_HASHES - 1 ) & (int) fabs( p->dist );
97
98         p->hash_chain = planehash[hash];
99         planehash[hash] = p - mapplanes + 1;
100 }
101
102 /*
103    ================
104    CreateNewFloatPlane
105    ================
106  */
107 int CreateNewFloatPlane( vec3_t normal, vec_t dist ){
108         plane_t *p, temp;
109
110         if ( VectorLength( normal ) < 0.5 ) {
111                 Sys_Printf( "FloatPlane: bad normal\n" );
112                 return -1;
113         }
114
115         // create a new plane
116         AUTOEXPAND_BY_REALLOC( mapplanes, nummapplanes + 1, allocatedmapplanes, 1024 );
117
118         p = &mapplanes[nummapplanes];
119         VectorCopy( normal, p->normal );
120         p->dist = dist;
121         p->type = ( p + 1 )->type = PlaneTypeForNormal( p->normal );
122
123         VectorSubtract( vec3_origin, normal, ( p + 1 )->normal );
124         ( p + 1 )->dist = -dist;
125
126         nummapplanes += 2;
127
128         // allways put axial planes facing positive first
129         if ( p->type < 3 ) {
130                 if ( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) {
131                         // flip order
132                         temp = *p;
133                         *p = *( p + 1 );
134                         *( p + 1 ) = temp;
135
136                         AddPlaneToHash( p );
137                         AddPlaneToHash( p + 1 );
138                         return nummapplanes - 1;
139                 }
140         }
141
142         AddPlaneToHash( p );
143         AddPlaneToHash( p + 1 );
144         return nummapplanes - 2;
145 }
146
147
148
149 /*
150    SnapNormal()
151    Snaps a near-axial normal vector.
152    Returns qtrue if and only if the normal was adjusted.
153  */
154
155 qboolean SnapNormal( vec3_t normal ){
156 #if Q3MAP2_EXPERIMENTAL_SNAP_NORMAL_FIX
157         int i;
158         qboolean adjusted = qfalse;
159
160         // A change from the original SnapNormal() is that we snap each
161         // component that's close to 0.  So for example if a normal is
162         // (0.707, 0.707, 0.0000001), it will get snapped to lie perfectly in the
163         // XY plane (its Z component will be set to 0 and its length will be
164         // normalized).  The original SnapNormal() didn't snap such vectors - it
165         // only snapped vectors that were near a perfect axis.
166
167         //adjusting vectors, that are near perfect axis, with bigger epsilon
168         //they cause precision errors
169
170
171         if ( ( normal[0] != 0.0 || normal[1] != 0.0 ) && fabs(normal[0]) < 0.00025 && fabs(normal[1]) < 0.00025){
172                 normal[0] = normal[1] = 0.0;
173                 adjusted = qtrue;
174         }
175         else if ( ( normal[0] != 0.0 || normal[2] != 0.0 ) && fabs(normal[0]) < 0.00025 && fabs(normal[2]) < 0.00025){
176                 normal[0] = normal[2] = 0.0;
177                 adjusted = qtrue;
178         }
179         else if ( ( normal[2] != 0.0 || normal[1] != 0.0 ) && fabs(normal[2]) < 0.00025 && fabs(normal[1]) < 0.00025){
180                 normal[2] = normal[1] = 0.0;
181                 adjusted = qtrue;
182         }
183
184
185         /*
186         for ( i=0; i<30; i++ )
187         {
188                 double x, y, z, length;
189                 x=(double) 1.0;
190                 y=(double) ( 0.00001 * i );
191                 z=(double) 0.0;
192
193                 Sys_Printf("(%6.18f %6.18f %6.18f)inNormal\n", x,y,z );
194
195                 length = sqrt( ( x * x ) + ( y * y ) + ( z * z ) );
196                 Sys_Printf("(%6.18f)length\n", length);
197                 x = (vec_t) ( x / length );
198                 y = (vec_t) ( y / length );
199                 z = (vec_t) ( z / length );
200                 Sys_Printf("(%6.18f %6.18f %6.18f)outNormal\n\n", x,y,z );
201         }
202         Error("vectorNormalize test completed");
203         */
204
205         for ( i = 0; i < 3; i++ )
206         {
207                 if ( normal[i] != 0.0 && -normalEpsilon < normal[i] && normal[i] < normalEpsilon ) {
208                         normal[i] = 0.0;
209                         adjusted = qtrue;
210                 }
211         }
212
213         if ( adjusted ) {
214                 VectorNormalize( normal, normal );
215                 return qtrue;
216         }
217         return qfalse;
218 #else
219         int i;
220
221         // I would suggest that you uncomment the following code and look at the
222         // results:
223
224         /*
225            Sys_Printf("normalEpsilon is %f\n", normalEpsilon);
226            for (i = 0;; i++)
227            {
228             normal[0] = 1.0;
229             normal[1] = 0.0;
230             normal[2] = i * 0.000001;
231             VectorNormalize(normal, normal);
232             if (1.0 - normal[0] >= normalEpsilon) {
233                 Sys_Printf("(%f %f %f)\n", normal[0], normal[1], normal[2]);
234                 Error("SnapNormal: test completed");
235             }
236            }
237          */
238
239         // When the normalEpsilon is 0.00001, the loop will break out when normal is
240         // (0.999990 0.000000 0.004469).  In other words, this is the vector closest
241         // to axial that will NOT be snapped.  Anything closer will be snaped.  Now,
242         // 0.004469 is close to 1/225.  The length of a circular quarter-arc of radius
243         // 1 is PI/2, or about 1.57.  And 0.004469/1.57 is about 0.0028, or about
244         // 1/350.  Expressed a different way, 1/350 is also about 0.26/90.
245         // This means is that a normal with an angle that is within 1/4 of a degree
246         // from axial will be "snapped".  My belief is that the person who wrote the
247         // code below did not intend it this way.  I think the person intended that
248         // the epsilon be measured against the vector components close to 0, not 1.0.
249         // I think the logic should be: if 2 of the normal components are within
250         // epsilon of 0, then the vector can be snapped to be perfectly axial.
251         // We may consider adjusting the epsilon to a larger value when we make this
252         // code fix.
253
254         for ( i = 0; i < 3; i++ )
255         {
256                 if ( fabs( normal[ i ] - 1 ) < normalEpsilon ) {
257                         VectorClear( normal );
258                         normal[ i ] = 1;
259                         return qtrue;
260                 }
261                 if ( fabs( normal[ i ] - -1 ) < normalEpsilon ) {
262                         VectorClear( normal );
263                         normal[ i ] = -1;
264                         return qtrue;
265                 }
266         }
267         return qfalse;
268 #endif
269 }
270
271
272
273 /*
274    SnapPlane()
275    snaps a plane to normal/distance epsilons
276  */
277
278 void SnapPlane( vec3_t normal, vec_t *dist ){
279 // SnapPlane disabled by LordHavoc because it often messes up collision
280 // brushes made from triangles of embedded models, and it has little effect
281 // on anything else (axial planes are usually derived from snapped points)
282 /*
283    SnapPlane reenabled by namespace because of multiple reports of
284    q3map2-crashes which were triggered by this patch.
285  */
286         SnapNormal( normal );
287
288         // TODO: Rambetter has some serious comments here as well.  First off,
289         // in the case where a normal is non-axial, there is nothing special
290         // about integer distances.  I would think that snapping a distance might
291         // make sense for axial normals, but I'm not so sure about snapping
292         // non-axial normals.  A shift by 0.01 in a plane, multiplied by a clipping
293         // against another plane that is 5 degrees off, and we introduce 0.1 error
294         // easily.  A 0.1 error in a vertex is where problems start to happen, such
295         // as disappearing triangles.
296
297         // Second, assuming we have snapped the normal above, let's say that the
298         // plane we just snapped was defined for some points that are actually
299         // quite far away from normal * dist.  Well, snapping the normal in this
300         // case means that we've just moved those points by potentially many units!
301         // Therefore, if we are going to snap the normal, we need to know the
302         // points we're snapping for so that the plane snaps with those points in
303         // mind (points remain close to the plane).
304
305         // I would like to know exactly which problems SnapPlane() is trying to
306         // solve so that we can better engineer it (I'm not saying that SnapPlane()
307         // should be removed altogether).  Fix all this snapping code at some point!
308
309         if ( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon ) {
310                 *dist = Q_rint( *dist );
311         }
312 }
313
314 /*
315    SnapPlaneImproved()
316    snaps a plane to normal/distance epsilons, improved code
317  */
318 void SnapPlaneImproved( vec3_t normal, vec_t *dist, int numPoints, const vec3_t *points ){
319         int i;
320         vec3_t center;
321         vec_t distNearestInt;
322
323         if ( SnapNormal( normal ) ) {
324                 if ( numPoints > 0 ) {
325                         // Adjust the dist so that the provided points don't drift away.
326                         VectorClear( center );
327                         for ( i = 0; i < numPoints; i++ )
328                         {
329                                 VectorAdd( center, points[i], center );
330                         }
331                         for ( i = 0; i < 3; i++ ) { center[i] = center[i] / numPoints; }
332                         *dist = DotProduct( normal, center );
333                 }
334         }
335
336         if ( VectorIsOnAxis( normal ) ) {
337                 // Only snap distance if the normal is an axis.  Otherwise there
338                 // is nothing "natural" about snapping the distance to an integer.
339                 distNearestInt = Q_rint( *dist );
340                 if ( -distanceEpsilon < *dist - distNearestInt && *dist - distNearestInt < distanceEpsilon ) {
341                         *dist = distNearestInt;
342                 }
343         }
344 }
345
346
347
348 /*
349    FindFloatPlane()
350    ydnar: changed to allow a number of test points to be supplied that
351    must be within an epsilon distance of the plane
352  */
353
354 int FindFloatPlane( vec3_t innormal, vec_t dist, int numPoints, vec3_t *points ) // NOTE: this has a side effect on the normal. Good or bad?
355
356 #ifdef USE_HASHING
357
358 {
359         int i, j, hash, h;
360         int pidx;
361         plane_t *p;
362         vec_t d;
363         vec3_t normal;
364
365         VectorCopy( innormal, normal );
366 #if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX
367         SnapPlaneImproved( normal, &dist, numPoints, (const vec3_t *) points );
368 #else
369         SnapPlane( normal, &dist );
370 #endif
371         /* hash the plane */
372         hash = ( PLANE_HASHES - 1 ) & (int) fabs( dist );
373
374         /* search the border bins as well */
375         for ( i = -1; i <= 1; i++ )
376         {
377                 h = ( hash + i ) & ( PLANE_HASHES - 1 );
378                 for ( pidx = planehash[ h ] - 1; pidx != -1; pidx = mapplanes[pidx].hash_chain - 1 )
379                 {
380                         p = &mapplanes[pidx];
381
382                         /* do standard plane compare */
383                         if ( !PlaneEqual( p, normal, dist ) ) {
384                                 continue;
385                         }
386
387                         /* ydnar: uncomment the following line for old-style plane finding */
388                         //%     return p - mapplanes;
389
390                         /* ydnar: test supplied points against this plane */
391                         for ( j = 0; j < numPoints; j++ )
392                         {
393                                 // NOTE: When dist approaches 2^16, the resolution of 32 bit floating
394                                 // point number is greatly decreased.  The distanceEpsilon cannot be
395                                 // very small when world coordinates extend to 2^16.  Making the
396                                 // dot product here in 64 bit land will not really help the situation
397                                 // because the error will already be carried in dist.
398                                 d = DotProduct( points[ j ], p->normal ) - p->dist;
399                                 d = fabs( d );
400                                 if ( d != 0.0 && d >= distanceEpsilon ) {
401                                         break; // Point is too far from plane.
402                                 }
403                         }
404
405                         /* found a matching plane */
406                         if ( j >= numPoints ) {
407                                 return p - mapplanes;
408                         }
409                 }
410         }
411
412         /* none found, so create a new one */
413         return CreateNewFloatPlane( normal, dist );
414 }
415
416 #else
417
418 {
419         int i;
420         plane_t *p;
421         vec3_t normal;
422
423         VectorCopy( innormal, normal );
424 #if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX
425         SnapPlaneImproved( normal, &dist, numPoints, (const vec3_t *) points );
426 #else
427         SnapPlane( normal, &dist );
428 #endif
429         for ( i = 0, p = mapplanes; i < nummapplanes; i++, p++ )
430         {
431                 if ( !PlaneEqual( p, normal, dist ) ) {
432                         continue;
433                 }
434
435                 /* ydnar: uncomment the following line for old-style plane finding */
436                 //%     return i;
437
438                 /* ydnar: test supplied points against this plane */
439                 for ( j = 0; j < numPoints; j++ )
440                 {
441                         d = DotProduct( points[ j ], p->normal ) - p->dist;
442                         if ( fabs( d ) > distanceEpsilon ) {
443                                 break;
444                         }
445                 }
446
447                 /* found a matching plane */
448                 if ( j >= numPoints ) {
449                         return i;
450                 }
451                 // TODO: Note that the non-USE_HASHING code does not compute epsilons
452                 // for the provided points.  It should do that.  I think this code
453                 // is unmaintained because nobody sets USE_HASHING to off.
454         }
455
456         return CreateNewFloatPlane( normal, dist );
457 }
458
459 #endif
460
461
462
463 /*
464    MapPlaneFromPoints()
465    takes 3 points and finds the plane they lie in
466  */
467
468 int MapPlaneFromPoints( vec3_t *p ){
469 #if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES
470         vec3_accu_t paccu[3];
471         vec3_accu_t t1, t2, normalAccu;
472         vec3_t normal;
473         vec_t dist;
474
475         VectorCopyRegularToAccu( p[0], paccu[0] );
476         VectorCopyRegularToAccu( p[1], paccu[1] );
477         VectorCopyRegularToAccu( p[2], paccu[2] );
478
479         VectorSubtractAccu( paccu[0], paccu[1], t1 );
480         VectorSubtractAccu( paccu[2], paccu[1], t2 );
481         CrossProductAccu( t1, t2, normalAccu );
482         VectorNormalizeAccu( normalAccu, normalAccu );
483         // TODO: A 32 bit float for the plane distance isn't enough resolution
484         // if the plane is 2^16 units away from the origin (the "epsilon" approaches
485         // 0.01 in that case).
486         dist = (vec_t) DotProductAccu( paccu[0], normalAccu );
487         VectorCopyAccuToRegular( normalAccu, normal );
488
489         return FindFloatPlane( normal, dist, 3, p );
490 #else
491         vec3_t t1, t2, normal;
492         vec_t dist;
493
494
495         /* calc plane normal */
496         VectorSubtract( p[ 0 ], p[ 1 ], t1 );
497         VectorSubtract( p[ 2 ], p[ 1 ], t2 );
498         CrossProduct( t1, t2, normal );
499         VectorNormalize( normal, normal );
500
501         /* calc plane distance */
502         dist = DotProduct( p[ 0 ], normal );
503
504         /* store the plane */
505         return FindFloatPlane( normal, dist, 3, p );
506 #endif
507 }
508
509
510
511 /*
512    SetBrushContents()
513    the content flags and compile flags on all sides of a brush should be the same
514  */
515
516 void SetBrushContents( brush_t *b ){
517         int contentFlags, compileFlags;
518         side_t      *s;
519         int i;
520         //%     qboolean        mixed;
521
522
523         /* get initial compile flags from first side */
524         s = &b->sides[ 0 ];
525         contentFlags = s->contentFlags;
526         compileFlags = s->compileFlags;
527         b->contentShader = s->shaderInfo;
528         //%     mixed = qfalse;
529
530         /* get the content/compile flags for every side in the brush */
531         //for ( i = 1; i < b->numsides; i++, s++ )
532         for ( i = 1; i < b->numsides; i++ )
533         {
534                 s = &b->sides[ i ];
535                 if ( s->shaderInfo == NULL ) {
536                         continue;
537                 }
538                 //%     if( s->contentFlags != contentFlags || s->compileFlags != compileFlags )
539                 //%             mixed = qtrue;
540
541                 contentFlags |= s->contentFlags;
542                 compileFlags |= s->compileFlags;
543
544                 /* resolve inconsistency, when brush content was determined by 1st face */
545                 if ( b->contentShader->compileFlags & C_LIQUID ){
546                         continue;
547                 }
548                 else if ( s->compileFlags & C_LIQUID ){
549                         b->contentShader = s->shaderInfo;
550                 }
551                 else if ( b->contentShader->compileFlags & C_FOG ){
552                         continue;
553                 }
554                 else if ( s->compileFlags & C_FOG ){
555                         b->contentShader = s->shaderInfo;
556                 }
557                 //playerclip
558                 else if ( b->contentShader->contentFlags & 0x10000 ){
559                         continue;
560                 }
561                 else if ( s->contentFlags & 0x10000 ){
562                         b->contentShader = s->shaderInfo;
563                 }
564                 else if (!( b->contentShader->compileFlags & C_SOLID )){
565                         continue;
566                 }
567                 else if (!( s->compileFlags & C_SOLID )){
568                         b->contentShader = s->shaderInfo;
569                 }
570         }
571
572         /* ydnar: getting rid of this stupid warning */
573         //%     if( mixed )
574         //%             Sys_FPrintf( SYS_VRB,"Entity %i, Brush %i: mixed face contentFlags\n", b->entitynum, b->brushnum );
575
576         /* check for detail & structural */
577         if ( ( compileFlags & C_DETAIL ) && ( compileFlags & C_STRUCTURAL ) ) {
578                 xml_Select( "Mixed detail and structural (defaulting to structural)", mapEnt->mapEntityNum, entitySourceBrushes, qfalse );
579                 compileFlags &= ~C_DETAIL;
580         }
581
582         /* the fulldetail flag will cause detail brushes to be treated like normal brushes */
583         if ( fulldetail ) {
584                 compileFlags &= ~C_DETAIL;
585         }
586
587         /* all translucent brushes that aren't specifically made structural will be detail */
588         if ( ( compileFlags & C_TRANSLUCENT ) && !( compileFlags & C_STRUCTURAL ) ) {
589                 compileFlags |= C_DETAIL;
590         }
591
592         /* detail? */
593         if ( compileFlags & C_DETAIL ) {
594                 c_detail++;
595                 b->detail = qtrue;
596         }
597         else
598         {
599                 c_structural++;
600                 b->detail = qfalse;
601         }
602
603         /* opaque? */
604         if ( compileFlags & C_TRANSLUCENT ) {
605                 b->opaque = qfalse;
606         }
607         else{
608                 b->opaque = qtrue;
609         }
610
611         /* areaportal? */
612         if ( compileFlags & C_AREAPORTAL ) {
613                 c_areaportals++;
614         }
615
616         /* set brush flags */
617         b->contentFlags = contentFlags;
618         b->compileFlags = compileFlags;
619 }
620
621
622
623 /*
624    AddBrushBevels()
625    adds any additional planes necessary to allow the brush being
626    built to be expanded against axial bounding boxes
627    ydnar 2003-01-20: added mrelusive fixes
628  */
629
630 void AddBrushBevels( void ){
631         int axis, dir;
632         int i, j, k, l, order;
633         side_t sidetemp;
634         side_t      *s, *s2;
635         winding_t   *w, *w2;
636         vec3_t normal;
637         float dist;
638         vec3_t vec, vec2;
639         float d, minBack;
640
641         //
642         // add the axial planes
643         //
644         order = 0;
645         for ( axis = 0; axis < 3; axis++ ) {
646                 for ( dir = -1; dir <= 1; dir += 2, order++ ) {
647                         // see if the plane is allready present
648                         for ( i = 0, s = buildBrush->sides; i < buildBrush->numsides; i++, s++ )
649                         {
650                                 /* ydnar: testing disabling of mre code */
651                                 #if 0
652                                 if ( dir > 0 ) {
653                                         if ( mapplanes[s->planenum].normal[axis] >= 0.9999f ) {
654                                                 break;
655                                         }
656                                 }
657                                 else {
658                                         if ( mapplanes[s->planenum].normal[axis] <= -0.9999f ) {
659                                                 break;
660                                         }
661                                 }
662                                 #else
663                                 if ( ( dir > 0 && mapplanes[ s->planenum ].normal[ axis ] == 1.0f ) ||
664                                          ( dir < 0 && mapplanes[ s->planenum ].normal[ axis ] == -1.0f ) ) {
665                                         break;
666                                 }
667                                 #endif
668                         }
669
670                         if ( i == buildBrush->numsides ) {
671                                 // add a new side
672                                 if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
673                                         xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue );
674                                 }
675                                 memset( s, 0, sizeof( *s ) );
676                                 buildBrush->numsides++;
677                                 VectorClear( normal );
678                                 normal[axis] = dir;
679
680                                 if ( dir == 1 ) {
681                                         /* ydnar: adding bevel plane snapping for fewer bsp planes */
682                                         if ( bevelSnap > 0 ) {
683                                                 dist = floor( buildBrush->maxs[ axis ] / bevelSnap ) * bevelSnap;
684                                         }
685                                         else{
686                                                 dist = buildBrush->maxs[ axis ];
687                                         }
688                                 }
689                                 else
690                                 {
691                                         /* ydnar: adding bevel plane snapping for fewer bsp planes */
692                                         if ( bevelSnap > 0 ) {
693                                                 dist = -ceil( buildBrush->mins[ axis ] / bevelSnap ) * bevelSnap;
694                                         }
695                                         else{
696                                                 dist = -buildBrush->mins[ axis ];
697                                         }
698                                 }
699
700                                 s->planenum = FindFloatPlane( normal, dist, 0, NULL );
701                                 s->contentFlags = buildBrush->sides[ 0 ].contentFlags;
702                                 s->bevel = qtrue;
703                                 c_boxbevels++;
704                         }
705
706                         // if the plane is not in it canonical order, swap it
707                         if ( i != order ) {
708                                 sidetemp = buildBrush->sides[order];
709                                 buildBrush->sides[order] = buildBrush->sides[i];
710                                 buildBrush->sides[i] = sidetemp;
711                         }
712                 }
713         }
714
715         //
716         // add the edge bevels
717         //
718         if ( buildBrush->numsides == 6 ) {
719                 return;     // pure axial
720         }
721
722         // test the non-axial plane edges
723         for ( i = 6; i < buildBrush->numsides; i++ ) {
724                 s = buildBrush->sides + i;
725                 w = s->winding;
726                 if ( !w ) {
727                         continue;
728                 }
729                 for ( j = 0; j < w->numpoints; j++ ) {
730                         k = ( j + 1 ) % w->numpoints;
731                         VectorSubtract( w->p[j], w->p[k], vec );
732                         if ( VectorNormalize( vec, vec ) < 0.5f ) {
733                                 continue;
734                         }
735                         SnapNormal( vec );
736                         for ( k = 0; k < 3; k++ ) {
737                                 if ( vec[k] == -1.0f || vec[k] == 1.0f || ( vec[k] == 0.0f && vec[( k + 1 ) % 3] == 0.0f ) ) {
738                                         break;  // axial
739                                 }
740                         }
741                         if ( k != 3 ) {
742                                 continue;   // only test non-axial edges
743                         }
744
745                         /* debug code */
746                         //%     Sys_Printf( "-------------\n" );
747
748                         // try the six possible slanted axials from this edge
749                         for ( axis = 0; axis < 3; axis++ ) {
750                                 for ( dir = -1; dir <= 1; dir += 2 ) {
751                                         // construct a plane
752                                         VectorClear( vec2 );
753                                         vec2[axis] = dir;
754                                         CrossProduct( vec, vec2, normal );
755                                         if ( VectorNormalize( normal, normal ) < 0.5f ) {
756                                                 continue;
757                                         }
758                                         dist = DotProduct( w->p[j], normal );
759
760                                         // if all the points on all the sides are
761                                         // behind this plane, it is a proper edge bevel
762                                         for ( k = 0; k < buildBrush->numsides; k++ ) {
763
764                                                 // if this plane has allready been used, skip it
765                                                 if ( PlaneEqual( &mapplanes[buildBrush->sides[k].planenum], normal, dist ) ) {
766                                                         break;
767                                                 }
768
769                                                 w2 = buildBrush->sides[k].winding;
770                                                 if ( !w2 ) {
771                                                         continue;
772                                                 }
773                                                 minBack = 0.0f;
774                                                 for ( l = 0; l < w2->numpoints; l++ ) {
775                                                         d = DotProduct( w2->p[l], normal ) - dist;
776                                                         if ( d > 0.1f ) {
777                                                                 break;  // point in front
778                                                         }
779                                                         if ( d < minBack ) {
780                                                                 minBack = d;
781                                                         }
782                                                 }
783                                                 // if some point was at the front
784                                                 if ( l != w2->numpoints ) {
785                                                         break;
786                                                 }
787
788                                                 // if no points at the back then the winding is on the bevel plane
789                                                 if ( minBack > -0.1f ) {
790                                                         //%     Sys_Printf( "On bevel plane\n" );
791                                                         break;
792                                                 }
793                                         }
794
795                                         if ( k != buildBrush->numsides ) {
796                                                 continue;   // wasn't part of the outer hull
797                                         }
798
799                                         /* debug code */
800                                         //%     Sys_Printf( "n = %f %f %f\n", normal[ 0 ], normal[ 1 ], normal[ 2 ] );
801
802                                         // add this plane
803                                         if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
804                                                 xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue );
805                                         }
806                                         s2 = &buildBrush->sides[buildBrush->numsides];
807                                         buildBrush->numsides++;
808                                         memset( s2, 0, sizeof( *s2 ) );
809
810                                         s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[ j ] );
811                                         s2->contentFlags = buildBrush->sides[0].contentFlags;
812                                         s2->bevel = qtrue;
813                                         c_edgebevels++;
814                                 }
815                         }
816                 }
817         }
818 }
819
820
821
822 /*
823    FinishBrush()
824    produces a final brush based on the buildBrush->sides array
825    and links it to the current entity
826  */
827
828 static void MergeOrigin( entity_t *ent, vec3_t origin ){
829         vec3_t adjustment;
830         char string[128];
831
832         /* we have not parsed the brush completely yet... */
833         GetVectorForKey( ent, "origin", ent->origin );
834
835         VectorMA( origin, -1, ent->originbrush_origin, adjustment );
836         VectorAdd( adjustment, ent->origin, ent->origin );
837         VectorCopy( origin, ent->originbrush_origin );
838
839         sprintf( string, "%f %f %f", ent->origin[0], ent->origin[1], ent->origin[2] );
840         SetKeyValue( ent, "origin", string );
841 }
842
843 brush_t *FinishBrush( qboolean noCollapseGroups ){
844         brush_t     *b;
845
846
847         /* create windings for sides and bounds for brush */
848         if ( !CreateBrushWindings( buildBrush ) ) {
849                 return NULL;
850         }
851
852         /* origin brushes are removed, but they set the rotation origin for the rest of the brushes in the entity.
853            after the entire entity is parsed, the planenums and texinfos will be adjusted for the origin brush */
854         if ( buildBrush->compileFlags & C_ORIGIN ) {
855                 vec3_t origin;
856
857                 Sys_Printf( "Entity %i, Brush %i: origin brush detected\n",
858                                         mapEnt->mapEntityNum, entitySourceBrushes );
859
860                 if ( numEntities == 1 ) {
861                         Sys_Printf( "Entity %i, Brush %i: origin brushes not allowed in world\n",
862                                                 mapEnt->mapEntityNum, entitySourceBrushes );
863                         return NULL;
864                 }
865
866                 VectorAdd( buildBrush->mins, buildBrush->maxs, origin );
867                 VectorScale( origin, 0.5, origin );
868
869                 MergeOrigin( &entities[ numEntities - 1 ], origin );
870
871                 /* don't keep this brush */
872                 return NULL;
873         }
874
875         /* determine if the brush is an area portal */
876         if ( buildBrush->compileFlags & C_AREAPORTAL ) {
877                 if ( numEntities != 1 ) {
878                         Sys_Printf( "Entity %i, Brush %i: areaportals only allowed in world\n", numEntities - 1, entitySourceBrushes );
879                         return NULL;
880                 }
881         }
882
883         /* add bevel planes */
884         if ( !noCollapseGroups ) {
885                 AddBrushBevels();
886         }
887
888         /* keep it */
889         b = CopyBrush( buildBrush );
890
891         /* set map entity and brush numbering */
892         b->entityNum = mapEnt->mapEntityNum;
893         b->brushNum = entitySourceBrushes;
894
895         /* set original */
896         b->original = b;
897
898         /* link opaque brushes to head of list, translucent brushes to end */
899         if ( b->opaque || mapEnt->lastBrush == NULL ) {
900                 b->next = mapEnt->brushes;
901                 mapEnt->brushes = b;
902                 if ( mapEnt->lastBrush == NULL ) {
903                         mapEnt->lastBrush = b;
904                 }
905         }
906         else
907         {
908                 b->next = NULL;
909                 mapEnt->lastBrush->next = b;
910                 mapEnt->lastBrush = b;
911         }
912
913         /* link colorMod volume brushes to the entity directly */
914         if ( b->contentShader != NULL &&
915                  b->contentShader->colorMod != NULL &&
916                  b->contentShader->colorMod->type == CM_VOLUME ) {
917                 b->nextColorModBrush = mapEnt->colorModBrushes;
918                 mapEnt->colorModBrushes = b;
919         }
920
921         /* return to sender */
922         return b;
923 }
924
925
926
927 /*
928    TextureAxisFromPlane()
929    determines best orthagonal axis to project a texture onto a wall
930    (must be identical in radiant!)
931  */
932
933 vec3_t baseaxis[18] =
934 {
935         {0,0,1}, {1,0,0}, {0,-1,0},         // floor
936         {0,0,-1}, {1,0,0}, {0,-1,0},        // ceiling
937         {1,0,0}, {0,1,0}, {0,0,-1},         // west wall
938         {-1,0,0}, {0,1,0}, {0,0,-1},        // east wall
939         {0,1,0}, {1,0,0}, {0,0,-1},         // south wall
940         {0,-1,0}, {1,0,0}, {0,0,-1}         // north wall
941 };
942
943 void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv ){
944         int bestaxis;
945         vec_t dot,best;
946         int i;
947
948         best = 0;
949         bestaxis = 0;
950
951         for ( i = 0 ; i < 6 ; i++ )
952         {
953                 dot = DotProduct( pln->normal, baseaxis[i * 3] );
954                 if ( dot > best + 0.0001f ) { /* ydnar: bug 637 fix, suggested by jmonroe */
955                         best = dot;
956                         bestaxis = i;
957                 }
958         }
959
960         VectorCopy( baseaxis[bestaxis * 3 + 1], xv );
961         VectorCopy( baseaxis[bestaxis * 3 + 2], yv );
962 }
963
964
965
966 /*
967    QuakeTextureVecs()
968    creates world-to-texture mapping vecs for crappy quake plane arrangements
969  */
970
971 void QuakeTextureVecs( plane_t *plane, vec_t shift[ 2 ], vec_t rotate, vec_t scale[ 2 ], vec_t mappingVecs[ 2 ][ 4 ] ){
972         vec3_t vecs[2];
973         int sv, tv;
974         vec_t ang, sinv, cosv;
975         vec_t ns, nt;
976         int i, j;
977
978
979         TextureAxisFromPlane( plane, vecs[0], vecs[1] );
980
981         if ( !scale[0] ) {
982                 scale[0] = 1;
983         }
984         if ( !scale[1] ) {
985                 scale[1] = 1;
986         }
987
988         // rotate axis
989         if ( rotate == 0 ) {
990                 sinv = 0 ; cosv = 1;
991         }
992         else if ( rotate == 90 ) {
993                 sinv = 1 ; cosv = 0;
994         }
995         else if ( rotate == 180 ) {
996                 sinv = 0 ; cosv = -1;
997         }
998         else if ( rotate == 270 ) {
999                 sinv = -1 ; cosv = 0;
1000         }
1001         else
1002         {
1003                 ang = rotate / 180 * Q_PI;
1004                 sinv = sin( ang );
1005                 cosv = cos( ang );
1006         }
1007
1008         if ( vecs[0][0] ) {
1009                 sv = 0;
1010         }
1011         else if ( vecs[0][1] ) {
1012                 sv = 1;
1013         }
1014         else{
1015                 sv = 2;
1016         }
1017
1018         if ( vecs[1][0] ) {
1019                 tv = 0;
1020         }
1021         else if ( vecs[1][1] ) {
1022                 tv = 1;
1023         }
1024         else{
1025                 tv = 2;
1026         }
1027
1028         for ( i = 0 ; i < 2 ; i++ ) {
1029                 ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
1030                 nt = sinv * vecs[i][sv] +  cosv * vecs[i][tv];
1031                 vecs[i][sv] = ns;
1032                 vecs[i][tv] = nt;
1033         }
1034
1035         for ( i = 0 ; i < 2 ; i++ )
1036                 for ( j = 0 ; j < 3 ; j++ )
1037                         mappingVecs[i][j] = vecs[i][j] / scale[i];
1038
1039         mappingVecs[0][3] = shift[0];
1040         mappingVecs[1][3] = shift[1];
1041 }
1042
1043
1044
1045 /*
1046    ParseRawBrush()
1047    parses the sides into buildBrush->sides[], nothing else.
1048    no validation, back plane removal, etc.
1049
1050    Timo - 08/26/99
1051    added brush epairs parsing ( ignoring actually )
1052    Timo - 08/04/99
1053    added exclusive brush primitive parsing
1054    Timo - 08/08/99
1055    support for old brush format back in
1056    NOTE: it would be "cleaner" to have seperate functions to parse between old and new brushes
1057  */
1058
1059 static void ParseRawBrush( qboolean onlyLights ){
1060         side_t          *side;
1061         vec3_t planePoints[ 3 ];
1062         int planenum;
1063         shaderInfo_t    *si;
1064         vec_t shift[ 2 ];
1065         vec_t rotate = 0;
1066         vec_t scale[ 2 ];
1067         char name[ MAX_QPATH ];
1068         char shader[ MAX_QPATH ];
1069         int flags;
1070
1071
1072         /* initial setup */
1073         buildBrush->numsides = 0;
1074         buildBrush->detail = qfalse;
1075
1076         /* bp */
1077         if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1078                 MatchToken( "{" );
1079         }
1080
1081         /* parse sides */
1082         while ( 1 )
1083         {
1084                 if ( !GetToken( qtrue ) ) {
1085                         break;
1086                 }
1087                 if ( !strcmp( token, "}" ) ) {
1088                         break;
1089                 }
1090
1091                 /* ttimo : bp: here we may have to jump over brush epairs (only used in editor) */
1092                 if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1093                         while ( 1 )
1094                         {
1095                                 if ( strcmp( token, "(" ) ) {
1096                                         GetToken( qfalse );
1097                                 }
1098                                 else{
1099                                         break;
1100                                 }
1101                                 GetToken( qtrue );
1102                         }
1103                 }
1104                 UnGetToken();
1105
1106                 /* test side count */
1107                 if ( buildBrush->numsides >= MAX_BUILD_SIDES ) {
1108                         xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue );
1109                 }
1110
1111                 /* add side */
1112                 side = &buildBrush->sides[ buildBrush->numsides ];
1113                 memset( side, 0, sizeof( *side ) );
1114                 buildBrush->numsides++;
1115
1116                 /* read the three point plane definition */
1117                 Parse1DMatrix( 3, planePoints[ 0 ] );
1118                 Parse1DMatrix( 3, planePoints[ 1 ] );
1119                 Parse1DMatrix( 3, planePoints[ 2 ] );
1120
1121                 /* bp: read the texture matrix */
1122                 if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1123                         Parse2DMatrix( 2, 3, (float*) side->texMat );
1124                 }
1125
1126                 /* read shader name */
1127                 GetToken( qfalse );
1128                 strcpy( name, token );
1129
1130                 /* bp */
1131                 if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) {
1132                         GetToken( qfalse );
1133                         shift[ 0 ] = atof( token );
1134                         GetToken( qfalse );
1135                         shift[ 1 ] = atof( token );
1136                         GetToken( qfalse );
1137                         rotate = atof( token );
1138                         GetToken( qfalse );
1139                         scale[ 0 ] = atof( token );
1140                         GetToken( qfalse );
1141                         scale[ 1 ] = atof( token );
1142                 }
1143
1144                 /* set default flags and values */
1145                 sprintf( shader, "textures/%s", name );
1146                 if ( onlyLights ) {
1147                         si = &shaderInfo[ 0 ];
1148                 }
1149                 else{
1150                         si = ShaderInfoForShader( shader );
1151                 }
1152                 side->shaderInfo = si;
1153                 side->surfaceFlags = si->surfaceFlags;
1154                 side->contentFlags = si->contentFlags;
1155                 side->compileFlags = si->compileFlags;
1156                 side->value = si->value;
1157
1158                 /* ydnar: gs mods: bias texture shift */
1159                 if ( si->globalTexture == qfalse ) {
1160                         shift[ 0 ] -= ( floor( shift[ 0 ] / si->shaderWidth ) * si->shaderWidth );
1161                         shift[ 1 ] -= ( floor( shift[ 1 ] / si->shaderHeight ) * si->shaderHeight );
1162                 }
1163
1164                 /*
1165                     historically, there are 3 integer values at the end of a brushside line in a .map file.
1166                     in quake 3, the only thing that mattered was the first of these three values, which
1167                     was previously the content flags. and only then did a single bit matter, the detail
1168                     bit. because every game has its own special flags for specifying detail, the
1169                     traditionally game-specified CONTENTS_DETAIL flag was overridden for Q3Map 2.3.0
1170                     by C_DETAIL, defined in q3map2.h. the value is exactly as it was before, but
1171                     is stored in compileFlags, as opposed to contentFlags, for multiple-game
1172                     portability. :sigh:
1173                  */
1174
1175                 if ( TokenAvailable() ) {
1176                         /* get detail bit from map content flags */
1177                         GetToken( qfalse );
1178                         flags = atoi( token );
1179                         if ( flags & C_DETAIL ) {
1180                                 side->compileFlags |= C_DETAIL;
1181                         }
1182
1183                         /* historical */
1184                         GetToken( qfalse );
1185                         //% td.flags = atoi( token );
1186                         GetToken( qfalse );
1187                         //% td.value = atoi( token );
1188                 }
1189
1190                 /* find the plane number */
1191                 planenum = MapPlaneFromPoints( planePoints );
1192                 side->planenum = planenum;
1193
1194                 /* bp: get the texture mapping for this texturedef / plane combination */
1195                 if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) {
1196                         QuakeTextureVecs( &mapplanes[ planenum ], shift, rotate, scale, side->vecs );
1197                 }
1198         }
1199
1200         /* bp */
1201         if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1202                 UnGetToken();
1203                 MatchToken( "}" );
1204                 MatchToken( "}" );
1205         }
1206 }
1207
1208
1209
1210 /*
1211    RemoveDuplicateBrushPlanes
1212    returns false if the brush has a mirrored set of planes,
1213    meaning it encloses no volume.
1214    also removes planes without any normal
1215  */
1216
1217 qboolean RemoveDuplicateBrushPlanes( brush_t *b ){
1218         int i, j, k;
1219         side_t      *sides;
1220
1221         sides = b->sides;
1222
1223         for ( i = 1 ; i < b->numsides ; i++ ) {
1224
1225                 // check for a degenerate plane
1226                 if ( sides[i].planenum == -1 ) {
1227                         xml_Select( "degenerate plane", b->entityNum, b->brushNum, qfalse );
1228                         // remove it
1229                         for ( k = i + 1 ; k < b->numsides ; k++ ) {
1230                                 sides[k - 1] = sides[k];
1231                         }
1232                         b->numsides--;
1233                         i--;
1234                         continue;
1235                 }
1236
1237                 // check for duplication and mirroring
1238                 for ( j = 0 ; j < i ; j++ ) {
1239                         if ( sides[i].planenum == sides[j].planenum ) {
1240                                 xml_Select( "duplicate plane", b->entityNum, b->brushNum, qfalse );
1241                                 // remove the second duplicate
1242                                 for ( k = i + 1 ; k < b->numsides ; k++ ) {
1243                                         sides[k - 1] = sides[k];
1244                                 }
1245                                 b->numsides--;
1246                                 i--;
1247                                 break;
1248                         }
1249
1250                         if ( sides[i].planenum == ( sides[j].planenum ^ 1 ) ) {
1251                                 // mirror plane, brush is invalid
1252                                 xml_Select( "mirrored plane", b->entityNum, b->brushNum, qfalse );
1253                                 return qfalse;
1254                         }
1255                 }
1256         }
1257         return qtrue;
1258 }
1259
1260
1261
1262 /*
1263    ParseBrush()
1264    parses a brush out of a map file and sets it up
1265  */
1266
1267 static void ParseBrush( qboolean onlyLights, qboolean noCollapseGroups ){
1268         /* parse the brush out of the map */
1269         ParseRawBrush( onlyLights );
1270
1271         /* only go this far? */
1272         if ( onlyLights ) {
1273                 return;
1274         }
1275
1276         /* set some defaults */
1277         buildBrush->portalareas[ 0 ] = -1;
1278         buildBrush->portalareas[ 1 ] = -1;
1279         buildBrush->entityNum = numMapEntities - 1;
1280         buildBrush->brushNum = entitySourceBrushes;
1281
1282         /* if there are mirrored planes, the entire brush is invalid */
1283         if ( !RemoveDuplicateBrushPlanes( buildBrush ) ) {
1284                 return;
1285         }
1286
1287         /* get the content for the entire brush */
1288         SetBrushContents( buildBrush );
1289
1290         /* allow detail brushes to be removed */
1291         if ( nodetail && ( buildBrush->compileFlags & C_DETAIL ) ) {
1292                 //%     FreeBrush( buildBrush );
1293                 return;
1294         }
1295
1296         /* allow liquid brushes to be removed */
1297         if ( nowater && ( buildBrush->compileFlags & C_LIQUID ) ) {
1298                 //%     FreeBrush( buildBrush );
1299                 return;
1300         }
1301
1302         /* ydnar: allow hint brushes to be removed */
1303         if ( noHint && ( buildBrush->compileFlags & C_HINT ) ) {
1304                 //%     FreeBrush( buildBrush );
1305                 return;
1306         }
1307
1308         /* finish the brush */
1309         FinishBrush( noCollapseGroups );
1310 }
1311
1312
1313
1314 /*
1315    MoveBrushesToWorld()
1316    takes all of the brushes from the current entity and
1317    adds them to the world's brush list
1318    (used by func_group)
1319  */
1320
1321 void AdjustBrushesForOrigin( entity_t *ent );
1322 void MoveBrushesToWorld( entity_t *ent ){
1323         brush_t     *b, *next;
1324         parseMesh_t *pm;
1325
1326         /* we need to undo the common/origin adjustment, and instead shift them by the entity key origin */
1327         VectorScale( ent->origin, -1, ent->originbrush_origin );
1328         AdjustBrushesForOrigin( ent );
1329         VectorClear( ent->originbrush_origin );
1330
1331         /* move brushes */
1332         for ( b = ent->brushes; b != NULL; b = next )
1333         {
1334                 /* get next brush */
1335                 next = b->next;
1336
1337                 /* link opaque brushes to head of list, translucent brushes to end */
1338                 if ( b->opaque || entities[ 0 ].lastBrush == NULL ) {
1339                         b->next = entities[ 0 ].brushes;
1340                         entities[ 0 ].brushes = b;
1341                         if ( entities[ 0 ].lastBrush == NULL ) {
1342                                 entities[ 0 ].lastBrush = b;
1343                         }
1344                 }
1345                 else
1346                 {
1347                         b->next = NULL;
1348                         entities[ 0 ].lastBrush->next = b;
1349                         entities[ 0 ].lastBrush = b;
1350                 }
1351         }
1352         ent->brushes = NULL;
1353
1354         /* ydnar: move colormod brushes */
1355         if ( ent->colorModBrushes != NULL ) {
1356                 for ( b = ent->colorModBrushes; b->nextColorModBrush != NULL; b = b->nextColorModBrush ) ;
1357
1358                 b->nextColorModBrush = entities[ 0 ].colorModBrushes;
1359                 entities[ 0 ].colorModBrushes = ent->colorModBrushes;
1360
1361                 ent->colorModBrushes = NULL;
1362         }
1363
1364         /* move patches */
1365         if ( ent->patches != NULL ) {
1366                 for ( pm = ent->patches; pm->next != NULL; pm = pm->next ) ;
1367
1368                 pm->next = entities[ 0 ].patches;
1369                 entities[ 0 ].patches = ent->patches;
1370
1371                 ent->patches = NULL;
1372         }
1373 }
1374
1375
1376
1377 /*
1378    AdjustBrushesForOrigin()
1379  */
1380
1381 void AdjustBrushesForOrigin( entity_t *ent ){
1382
1383         int i;
1384         side_t      *s;
1385         vec_t newdist;
1386         brush_t     *b;
1387         parseMesh_t *p;
1388
1389         /* walk brush list */
1390         for ( b = ent->brushes; b != NULL; b = b->next )
1391         {
1392                 /* offset brush planes */
1393                 for ( i = 0; i < b->numsides; i++ )
1394                 {
1395                         /* get brush side */
1396                         s = &b->sides[ i ];
1397
1398                         /* offset side plane */
1399                         newdist = mapplanes[ s->planenum ].dist - DotProduct( mapplanes[ s->planenum ].normal, ent->originbrush_origin );
1400
1401                         /* find a new plane */
1402                         s->planenum = FindFloatPlane( mapplanes[ s->planenum ].normal, newdist, 0, NULL );
1403                 }
1404
1405                 /* rebuild brush windings (ydnar: just offsetting the winding above should be fine) */
1406                 CreateBrushWindings( b );
1407         }
1408
1409         /* walk patch list */
1410         for ( p = ent->patches; p != NULL; p = p->next )
1411         {
1412                 for ( i = 0; i < ( p->mesh.width * p->mesh.height ); i++ )
1413                         VectorSubtract( p->mesh.verts[ i ].xyz, ent->originbrush_origin, p->mesh.verts[ i ].xyz );
1414         }
1415 }
1416
1417
1418
1419 /*
1420    SetEntityBounds() - ydnar
1421    finds the bounds of an entity's brushes (necessary for terrain-style generic metashaders)
1422  */
1423
1424 void SetEntityBounds( entity_t *e ){
1425         int i;
1426         brush_t *b;
1427         parseMesh_t *p;
1428         vec3_t mins, maxs;
1429         const char  *value;
1430
1431
1432
1433
1434         /* walk the entity's brushes/patches and determine bounds */
1435         ClearBounds( mins, maxs );
1436         for ( b = e->brushes; b; b = b->next )
1437         {
1438                 AddPointToBounds( b->mins, mins, maxs );
1439                 AddPointToBounds( b->maxs, mins, maxs );
1440         }
1441         for ( p = e->patches; p; p = p->next )
1442         {
1443                 for ( i = 0; i < ( p->mesh.width * p->mesh.height ); i++ )
1444                         AddPointToBounds( p->mesh.verts[ i ].xyz, mins, maxs );
1445         }
1446
1447         /* try to find explicit min/max key */
1448         value = ValueForKey( e, "min" );
1449         if ( value[ 0 ] != '\0' ) {
1450                 GetVectorForKey( e, "min", mins );
1451         }
1452         value = ValueForKey( e, "max" );
1453         if ( value[ 0 ] != '\0' ) {
1454                 GetVectorForKey( e, "max", maxs );
1455         }
1456
1457         /* store the bounds */
1458         for ( b = e->brushes; b; b = b->next )
1459         {
1460                 VectorCopy( mins, b->eMins );
1461                 VectorCopy( maxs, b->eMaxs );
1462         }
1463         for ( p = e->patches; p; p = p->next )
1464         {
1465                 VectorCopy( mins, p->eMins );
1466                 VectorCopy( maxs, p->eMaxs );
1467         }
1468 }
1469
1470
1471
1472 /*
1473    LoadEntityIndexMap() - ydnar
1474    based on LoadAlphaMap() from terrain.c, a little more generic
1475  */
1476
1477 void LoadEntityIndexMap( entity_t *e ){
1478         int i, size, numLayers, w, h;
1479         const char      *value, *indexMapFilename, *shader;
1480         char ext[ MAX_QPATH ], offset[ 4096 ], *search, *space;
1481         byte            *pixels;
1482         unsigned int    *pixels32;
1483         indexMap_t      *im;
1484         brush_t         *b;
1485         parseMesh_t     *p;
1486
1487
1488         /* this only works with bmodel ents */
1489         if ( e->brushes == NULL && e->patches == NULL ) {
1490                 return;
1491         }
1492
1493         /* determine if there is an index map (support legacy "alphamap" key as well) */
1494         value = ValueForKey( e, "_indexmap" );
1495         if ( value[ 0 ] == '\0' ) {
1496                 value = ValueForKey( e, "alphamap" );
1497         }
1498         if ( value[ 0 ] == '\0' ) {
1499                 return;
1500         }
1501         indexMapFilename = value;
1502
1503         /* get number of layers (support legacy "layers" key as well) */
1504         value = ValueForKey( e, "_layers" );
1505         if ( value[ 0 ] == '\0' ) {
1506                 value = ValueForKey( e, "layers" );
1507         }
1508         if ( value[ 0 ] == '\0' ) {
1509                 Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has missing \"_layers\" or \"layers\" key\n", indexMapFilename );
1510                 Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
1511                 return;
1512         }
1513         numLayers = atoi( value );
1514         if ( numLayers < 1 ) {
1515                 Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has < 1 layer (%d)\n", indexMapFilename, numLayers );
1516                 Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
1517                 return;
1518         }
1519
1520         /* get base shader name (support legacy "shader" key as well) */
1521         value = ValueForKey( mapEnt, "_shader" );
1522         if ( value[ 0 ] == '\0' ) {
1523                 value = ValueForKey( e, "shader" );
1524         }
1525         if ( value[ 0 ] == '\0' ) {
1526                 Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has missing \"_shader\" or \"shader\" key\n", indexMapFilename );
1527                 Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
1528                 return;
1529         }
1530         shader = value;
1531
1532         /* note it */
1533         Sys_FPrintf( SYS_VRB, "Entity %d (%s) has shader index map \"%s\"\n",  mapEnt->mapEntityNum, ValueForKey( e, "classname" ), indexMapFilename );
1534
1535         /* get index map file extension */
1536         ExtractFileExtension( indexMapFilename, ext );
1537
1538         /* handle tga image */
1539         if ( !Q_stricmp( ext, "tga" ) ) {
1540                 /* load it */
1541                 Load32BitImage( indexMapFilename, &pixels32, &w, &h );
1542
1543                 /* convert to bytes */
1544                 size = w * h;
1545                 pixels = safe_malloc( size );
1546                 for ( i = 0; i < size; i++ )
1547                 {
1548                         pixels[ i ] = ( ( pixels32[ i ] & 0xFF ) * numLayers ) / 256;
1549                         if ( pixels[ i ] >= numLayers ) {
1550                                 pixels[ i ] = numLayers - 1;
1551                         }
1552                 }
1553
1554                 /* free the 32 bit image */
1555                 free( pixels32 );
1556         }
1557         else
1558         {
1559                 /* load it */
1560                 Load256Image( indexMapFilename, &pixels, NULL, &w, &h );
1561
1562                 /* debug code */
1563                 //%     Sys_Printf( "-------------------------------" );
1564
1565                 /* fix up out-of-range values */
1566                 size = w * h;
1567                 for ( i = 0; i < size; i++ )
1568                 {
1569                         if ( pixels[ i ] >= numLayers ) {
1570                                 pixels[ i ] = numLayers - 1;
1571                         }
1572
1573                         /* debug code */
1574                         //%     if( (i % w) == 0 )
1575                         //%             Sys_Printf( "\n" );
1576                         //%     Sys_Printf( "%c", pixels[ i ] + '0' );
1577                 }
1578
1579                 /* debug code */
1580                 //%     Sys_Printf( "\n-------------------------------\n" );
1581         }
1582
1583         /* the index map must be at least 2x2 pixels */
1584         if ( w < 2 || h < 2 ) {
1585                 Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" is smaller than 2x2 pixels\n", indexMapFilename );
1586                 Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" );
1587                 free( pixels );
1588                 return;
1589         }
1590
1591         /* create a new index map */
1592         im = safe_malloc0( sizeof( *im ) );
1593
1594         /* set it up */
1595         im->w = w;
1596         im->h = h;
1597         im->numLayers = numLayers;
1598         strcpy( im->name, indexMapFilename );
1599         strcpy( im->shader, shader );
1600         im->pixels = pixels;
1601
1602         /* get height offsets */
1603         value = ValueForKey( mapEnt, "_offsets" );
1604         if ( value[ 0 ] == '\0' ) {
1605                 value = ValueForKey( e, "offsets" );
1606         }
1607         if ( value[ 0 ] != '\0' ) {
1608                 /* value is a space-seperated set of numbers */
1609                 strcpy( offset, value );
1610                 search = offset;
1611
1612                 /* get each value */
1613                 for ( i = 0; i < 256 && *search != '\0'; i++ )
1614                 {
1615                         space = strstr( search, " " );
1616                         if ( space != NULL ) {
1617                                 *space = '\0';
1618                         }
1619                         im->offsets[ i ] = atof( search );
1620                         if ( space == NULL ) {
1621                                 break;
1622                         }
1623                         search = space + 1;
1624                 }
1625         }
1626
1627         /* store the index map in every brush/patch in the entity */
1628         for ( b = e->brushes; b != NULL; b = b->next )
1629                 b->im = im;
1630         for ( p = e->patches; p != NULL; p = p->next )
1631                 p->im = im;
1632 }
1633
1634
1635
1636
1637
1638
1639
1640 /*
1641    ParseMapEntity()
1642    parses a single entity out of a map file
1643  */
1644
1645 static qboolean ParseMapEntity( qboolean onlyLights, qboolean noCollapseGroups ){
1646         epair_t         *ep;
1647         const char      *classname, *value;
1648         float lightmapScale, shadeAngle;
1649         int lightmapSampleSize;
1650         char shader[ MAX_QPATH ];
1651         shaderInfo_t    *celShader = NULL;
1652         brush_t         *brush;
1653         parseMesh_t     *patch;
1654         qboolean funcGroup;
1655         int castShadows, recvShadows;
1656
1657
1658         /* eof check */
1659         if ( !GetToken( qtrue ) ) {
1660                 return qfalse;
1661         }
1662
1663         /* conformance check */
1664         if ( strcmp( token, "{" ) ) {
1665                 Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n"
1666                                         "Continuing to process map, but resulting BSP may be invalid.\n",
1667                                         token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] );
1668                 return qfalse;
1669         }
1670
1671         /* range check */
1672         AUTOEXPAND_BY_REALLOC( entities, numEntities, allocatedEntities, 32 );
1673
1674         /* setup */
1675         entitySourceBrushes = 0;
1676         mapEnt = &entities[ numEntities ];
1677         numEntities++;
1678         memset( mapEnt, 0, sizeof( *mapEnt ) );
1679
1680         /* ydnar: true entity numbering */
1681         mapEnt->mapEntityNum = numMapEntities;
1682         numMapEntities++;
1683
1684         /* loop */
1685         while ( 1 )
1686         {
1687                 /* get initial token */
1688                 if ( !GetToken( qtrue ) ) {
1689                         Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: EOF without closing brace\n"
1690                                                 "Continuing to process map, but resulting BSP may be invalid.\n" );
1691                         return qfalse;
1692                 }
1693
1694                 if ( !strcmp( token, "}" ) ) {
1695                         break;
1696                 }
1697
1698                 if ( !strcmp( token, "{" ) ) {
1699                         /* parse a brush or patch */
1700                         if ( !GetToken( qtrue ) ) {
1701                                 break;
1702                         }
1703
1704                         /* check */
1705                         if ( !strcmp( token, "patchDef2" ) ) {
1706                                 numMapPatches++;
1707                                 ParsePatch( onlyLights );
1708                         }
1709                         else if ( !strcmp( token, "terrainDef" ) ) {
1710                                 //% ParseTerrain();
1711                                 Sys_FPrintf( SYS_WRN, "WARNING: Terrain entity parsing not supported in this build.\n" ); /* ydnar */
1712                         }
1713                         else if ( !strcmp( token, "brushDef" ) ) {
1714                                 if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) {
1715                                         Error( "Old brush format not allowed in new brush format map" );
1716                                 }
1717                                 g_bBrushPrimit = BPRIMIT_NEWBRUSHES;
1718
1719                                 /* parse brush primitive */
1720                                 ParseBrush( onlyLights, noCollapseGroups );
1721                         }
1722                         else
1723                         {
1724                                 if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1725                                         Error( "New brush format not allowed in old brush format map" );
1726                                 }
1727                                 g_bBrushPrimit = BPRIMIT_OLDBRUSHES;
1728
1729                                 /* parse old brush format */
1730                                 UnGetToken();
1731                                 ParseBrush( onlyLights, noCollapseGroups );
1732                         }
1733                         entitySourceBrushes++;
1734                 }
1735                 else
1736                 {
1737                         /* parse a key / value pair */
1738                         ep = ParseEPair();
1739
1740                         /* ydnar: 2002-07-06 fixed wolf bug with empty epairs */
1741                         if ( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' ) {
1742                                 ep->next = mapEnt->epairs;
1743                                 mapEnt->epairs = ep;
1744                         }
1745                 }
1746         }
1747
1748         /* ydnar: get classname */
1749         classname = ValueForKey( mapEnt, "classname" );
1750
1751         /* ydnar: only lights? */
1752         if ( onlyLights && Q_strncasecmp( classname, "light", 5 ) ) {
1753                 numEntities--;
1754                 return qtrue;
1755         }
1756
1757         /* ydnar: determine if this is a func_group */
1758         if ( !Q_stricmp( "func_group", classname ) ) {
1759                 funcGroup = qtrue;
1760         }
1761         else{
1762                 funcGroup = qfalse;
1763         }
1764
1765         /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */
1766         if ( funcGroup || mapEnt->mapEntityNum == 0 ) {
1767                 //%     Sys_Printf( "World:  %d\n", mapEnt->mapEntityNum );
1768                 castShadows = WORLDSPAWN_CAST_SHADOWS;
1769                 recvShadows = WORLDSPAWN_RECV_SHADOWS;
1770         }
1771
1772         /* other entities don't cast any shadows, but recv worldspawn shadows */
1773         else
1774         {
1775                 //%     Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum );
1776                 castShadows = ENTITY_CAST_SHADOWS;
1777                 recvShadows = ENTITY_RECV_SHADOWS;
1778         }
1779
1780         /* get explicit shadow flags */
1781         GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows );
1782
1783         /* vortex: added _ls key (short name of lightmapscale) */
1784         /* ydnar: get lightmap scaling value for this entity */
1785         lightmapScale = 0.0f;
1786         if ( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) ||
1787                  strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) ||
1788                  strcmp( "", ValueForKey( mapEnt, "_ls" ) ) ) {
1789                 /* get lightmap scale from entity */
1790                 lightmapScale = FloatForKey( mapEnt, "lightmapscale" );
1791                 if ( lightmapScale <= 0.0f ) {
1792                         lightmapScale = FloatForKey( mapEnt, "_lightmapscale" );
1793                 }
1794                 if ( lightmapScale <= 0.0f ) {
1795                         lightmapScale = FloatForKey( mapEnt, "_ls" );
1796                 }
1797                 if ( lightmapScale < 0.0f ) {
1798                         lightmapScale = 0.0f;
1799                 }
1800                 if ( lightmapScale > 0.0f ) {
1801                         Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale );
1802                 }
1803         }
1804
1805         /* ydnar: get cel shader :) for this entity */
1806         value = ValueForKey( mapEnt, "_celshader" );
1807         if ( value[ 0 ] == '\0' ) {
1808                 value = ValueForKey( &entities[ 0 ], "_celshader" );
1809         }
1810         if ( value[ 0 ] != '\0' ) {
1811                 if ( strcmp( value, "none" ) ) {
1812                         sprintf( shader, "textures/%s", value );
1813                         celShader = ShaderInfoForShader( shader );
1814                         Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader );
1815                 }
1816                 else
1817                 {
1818                         celShader = NULL;
1819                 }
1820         }
1821         else{
1822                 celShader = ( *globalCelShader ? ShaderInfoForShader( globalCelShader ) : NULL );
1823         }
1824
1825         /* jal : entity based _shadeangle */
1826         shadeAngle = 0.0f;
1827         if ( strcmp( "", ValueForKey( mapEnt, "_shadeangle" ) ) ) {
1828                 shadeAngle = FloatForKey( mapEnt, "_shadeangle" );
1829         }
1830         /* vortex' aliases */
1831         else if ( strcmp( "", ValueForKey( mapEnt, "_smoothnormals" ) ) ) {
1832                 shadeAngle = FloatForKey( mapEnt, "_smoothnormals" );
1833         }
1834         else if ( strcmp( "", ValueForKey( mapEnt, "_sn" ) ) ) {
1835                 shadeAngle = FloatForKey( mapEnt, "_sn" );
1836         }
1837         else if ( strcmp( "", ValueForKey( mapEnt, "_sa" ) ) ) {
1838                 shadeAngle = FloatForKey( mapEnt, "_sa" );
1839         }
1840         else if ( strcmp( "", ValueForKey( mapEnt, "_smooth" ) ) ) {
1841                 shadeAngle = FloatForKey( mapEnt, "_smooth" );
1842         }
1843
1844         if ( shadeAngle < 0.0f ) {
1845                 shadeAngle = 0.0f;
1846         }
1847
1848         if ( shadeAngle > 0.0f ) {
1849                 Sys_Printf( "Entity %d (%s) has shading angle of %.4f\n", mapEnt->mapEntityNum, classname, shadeAngle );
1850         }
1851
1852         /* jal : entity based _samplesize */
1853         lightmapSampleSize = 0;
1854         if ( strcmp( "", ValueForKey( mapEnt, "_lightmapsamplesize" ) ) ) {
1855                 lightmapSampleSize = IntForKey( mapEnt, "_lightmapsamplesize" );
1856         }
1857         else if ( strcmp( "", ValueForKey( mapEnt, "_samplesize" ) ) ) {
1858                 lightmapSampleSize = IntForKey( mapEnt, "_samplesize" );
1859         }
1860         else if ( strcmp( "", ValueForKey( mapEnt, "_ss" ) ) ) {
1861                 lightmapSampleSize = IntForKey( mapEnt, "_ss" );
1862         }
1863
1864         if ( lightmapSampleSize < 0 ) {
1865                 lightmapSampleSize = 0;
1866         }
1867
1868         if ( lightmapSampleSize > 0 ) {
1869                 Sys_Printf( "Entity %d (%s) has lightmap sample size of %d\n", mapEnt->mapEntityNum, classname, lightmapSampleSize );
1870         }
1871
1872         /* attach stuff to everything in the entity */
1873         for ( brush = mapEnt->brushes; brush != NULL; brush = brush->next )
1874         {
1875                 brush->entityNum = mapEnt->mapEntityNum;
1876                 brush->castShadows = castShadows;
1877                 brush->recvShadows = recvShadows;
1878                 brush->lightmapSampleSize = lightmapSampleSize;
1879                 brush->lightmapScale = lightmapScale;
1880                 brush->celShader = celShader;
1881                 brush->shadeAngleDegrees = shadeAngle;
1882         }
1883
1884         for ( patch = mapEnt->patches; patch != NULL; patch = patch->next )
1885         {
1886                 patch->entityNum = mapEnt->mapEntityNum;
1887                 patch->castShadows = castShadows;
1888                 patch->recvShadows = recvShadows;
1889                 patch->lightmapSampleSize = lightmapSampleSize;
1890                 patch->lightmapScale = lightmapScale;
1891                 patch->celShader = celShader;
1892         }
1893
1894         /* ydnar: gs mods: set entity bounds */
1895         SetEntityBounds( mapEnt );
1896
1897         /* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */
1898         LoadEntityIndexMap( mapEnt );
1899
1900         /* get entity origin and adjust brushes */
1901         GetVectorForKey( mapEnt, "origin", mapEnt->origin );
1902         if ( mapEnt->originbrush_origin[ 0 ] || mapEnt->originbrush_origin[ 1 ] || mapEnt->originbrush_origin[ 2 ] ) {
1903                 AdjustBrushesForOrigin( mapEnt );
1904         }
1905
1906         /* group_info entities are just for editor grouping (fixme: leak!) */
1907         if ( !noCollapseGroups && !Q_stricmp( "group_info", classname ) ) {
1908                 numEntities--;
1909                 return qtrue;
1910         }
1911
1912         /* group entities are just for editor convenience, toss all brushes into worldspawn */
1913         if ( !noCollapseGroups && funcGroup ) {
1914                 MoveBrushesToWorld( mapEnt );
1915                 numEntities--;
1916                 return qtrue;
1917         }
1918
1919         /* done */
1920         return qtrue;
1921 }
1922
1923
1924
1925 /*
1926    LoadMapFile()
1927    loads a map file into a list of entities
1928  */
1929
1930 void LoadMapFile( char *filename, qboolean onlyLights, qboolean noCollapseGroups ){
1931         FILE        *file;
1932         brush_t     *b;
1933         int oldNumEntities = 0, numMapBrushes;
1934
1935
1936         /* note it */
1937         Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" );
1938         Sys_Printf( "Loading %s\n", filename );
1939
1940         /* hack */
1941         file = SafeOpenRead( filename );
1942         fclose( file );
1943
1944         /* load the map file */
1945         LoadScriptFile( filename, -1 );
1946
1947         /* setup */
1948         if ( onlyLights ) {
1949                 oldNumEntities = numEntities;
1950         }
1951         else{
1952                 numEntities = 0;
1953         }
1954
1955         /* initial setup */
1956         numMapDrawSurfs = 0;
1957         c_detail = 0;
1958         g_bBrushPrimit = BPRIMIT_UNDEFINED;
1959
1960         /* allocate a very large temporary brush for building the brushes as they are loaded */
1961         buildBrush = AllocBrush( MAX_BUILD_SIDES );
1962
1963         /* parse the map file */
1964         while ( ParseMapEntity( onlyLights, noCollapseGroups ) ) ;
1965
1966         /* light loading */
1967         if ( onlyLights ) {
1968                 /* emit some statistics */
1969                 Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities );
1970         }
1971         else
1972         {
1973                 /* set map bounds */
1974                 ClearBounds( mapMins, mapMaxs );
1975                 for ( b = entities[ 0 ].brushes; b; b = b->next )
1976                 {
1977                         AddPointToBounds( b->mins, mapMins, mapMaxs );
1978                         AddPointToBounds( b->maxs, mapMins, mapMaxs );
1979                 }
1980
1981                 /* get brush counts */
1982                 numMapBrushes = CountBrushList( entities[ 0 ].brushes );
1983                 if ( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 ) {
1984                         Sys_FPrintf( SYS_WRN, "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" );
1985                 }
1986
1987                 /* emit some statistics */
1988                 Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes );
1989                 Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail );
1990                 Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches );
1991                 Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels );
1992                 Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels );
1993                 Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities );
1994                 Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes );
1995                 Sys_Printf( "%9d areaportals\n", c_areaportals );
1996                 Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n",
1997                                         mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ],
1998                                         mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ] );
1999
2000                 /* write bogus map */
2001                 if ( fakemap ) {
2002                         WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes );
2003                 }
2004         }
2005 }