]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/map.c
Merge commit '3a78d902017a780e65f21f12c709aa746dfcab84' into garux-merge
[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_malloc( sizeof( *im ) );
1593         memset( im, 0, sizeof( *im ) );
1594
1595         /* set it up */
1596         im->w = w;
1597         im->h = h;
1598         im->numLayers = numLayers;
1599         strcpy( im->name, indexMapFilename );
1600         strcpy( im->shader, shader );
1601         im->pixels = pixels;
1602
1603         /* get height offsets */
1604         value = ValueForKey( mapEnt, "_offsets" );
1605         if ( value[ 0 ] == '\0' ) {
1606                 value = ValueForKey( e, "offsets" );
1607         }
1608         if ( value[ 0 ] != '\0' ) {
1609                 /* value is a space-seperated set of numbers */
1610                 strcpy( offset, value );
1611                 search = offset;
1612
1613                 /* get each value */
1614                 for ( i = 0; i < 256 && *search != '\0'; i++ )
1615                 {
1616                         space = strstr( search, " " );
1617                         if ( space != NULL ) {
1618                                 *space = '\0';
1619                         }
1620                         im->offsets[ i ] = atof( search );
1621                         if ( space == NULL ) {
1622                                 break;
1623                         }
1624                         search = space + 1;
1625                 }
1626         }
1627
1628         /* store the index map in every brush/patch in the entity */
1629         for ( b = e->brushes; b != NULL; b = b->next )
1630                 b->im = im;
1631         for ( p = e->patches; p != NULL; p = p->next )
1632                 p->im = im;
1633 }
1634
1635
1636
1637
1638
1639
1640
1641 /*
1642    ParseMapEntity()
1643    parses a single entity out of a map file
1644  */
1645
1646 static qboolean ParseMapEntity( qboolean onlyLights, qboolean noCollapseGroups ){
1647         epair_t         *ep;
1648         const char      *classname, *value;
1649         float lightmapScale, shadeAngle;
1650         int lightmapSampleSize;
1651         char shader[ MAX_QPATH ];
1652         shaderInfo_t    *celShader = NULL;
1653         brush_t         *brush;
1654         parseMesh_t     *patch;
1655         qboolean funcGroup;
1656         int castShadows, recvShadows;
1657
1658
1659         /* eof check */
1660         if ( !GetToken( qtrue ) ) {
1661                 return qfalse;
1662         }
1663
1664         /* conformance check */
1665         if ( strcmp( token, "{" ) ) {
1666                 Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n"
1667                                         "Continuing to process map, but resulting BSP may be invalid.\n",
1668                                         token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] );
1669                 return qfalse;
1670         }
1671
1672         /* range check */
1673         AUTOEXPAND_BY_REALLOC( entities, numEntities, allocatedEntities, 32 );
1674
1675         /* setup */
1676         entitySourceBrushes = 0;
1677         mapEnt = &entities[ numEntities ];
1678         numEntities++;
1679         memset( mapEnt, 0, sizeof( *mapEnt ) );
1680
1681         /* ydnar: true entity numbering */
1682         mapEnt->mapEntityNum = numMapEntities;
1683         numMapEntities++;
1684
1685         /* loop */
1686         while ( 1 )
1687         {
1688                 /* get initial token */
1689                 if ( !GetToken( qtrue ) ) {
1690                         Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: EOF without closing brace\n"
1691                                                 "Continuing to process map, but resulting BSP may be invalid.\n" );
1692                         return qfalse;
1693                 }
1694
1695                 if ( !strcmp( token, "}" ) ) {
1696                         break;
1697                 }
1698
1699                 if ( !strcmp( token, "{" ) ) {
1700                         /* parse a brush or patch */
1701                         if ( !GetToken( qtrue ) ) {
1702                                 break;
1703                         }
1704
1705                         /* check */
1706                         if ( !strcmp( token, "patchDef2" ) ) {
1707                                 numMapPatches++;
1708                                 ParsePatch( onlyLights );
1709                         }
1710                         else if ( !strcmp( token, "terrainDef" ) ) {
1711                                 //% ParseTerrain();
1712                                 Sys_FPrintf( SYS_WRN, "WARNING: Terrain entity parsing not supported in this build.\n" ); /* ydnar */
1713                         }
1714                         else if ( !strcmp( token, "brushDef" ) ) {
1715                                 if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) {
1716                                         Error( "Old brush format not allowed in new brush format map" );
1717                                 }
1718                                 g_bBrushPrimit = BPRIMIT_NEWBRUSHES;
1719
1720                                 /* parse brush primitive */
1721                                 ParseBrush( onlyLights, noCollapseGroups );
1722                         }
1723                         else
1724                         {
1725                                 if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) {
1726                                         Error( "New brush format not allowed in old brush format map" );
1727                                 }
1728                                 g_bBrushPrimit = BPRIMIT_OLDBRUSHES;
1729
1730                                 /* parse old brush format */
1731                                 UnGetToken();
1732                                 ParseBrush( onlyLights, noCollapseGroups );
1733                         }
1734                         entitySourceBrushes++;
1735                 }
1736                 else
1737                 {
1738                         /* parse a key / value pair */
1739                         ep = ParseEPair();
1740
1741                         /* ydnar: 2002-07-06 fixed wolf bug with empty epairs */
1742                         if ( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' ) {
1743                                 ep->next = mapEnt->epairs;
1744                                 mapEnt->epairs = ep;
1745                         }
1746                 }
1747         }
1748
1749         /* ydnar: get classname */
1750         classname = ValueForKey( mapEnt, "classname" );
1751
1752         /* ydnar: only lights? */
1753         if ( onlyLights && Q_strncasecmp( classname, "light", 5 ) ) {
1754                 numEntities--;
1755                 return qtrue;
1756         }
1757
1758         /* ydnar: determine if this is a func_group */
1759         if ( !Q_stricmp( "func_group", classname ) ) {
1760                 funcGroup = qtrue;
1761         }
1762         else{
1763                 funcGroup = qfalse;
1764         }
1765
1766         /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */
1767         if ( funcGroup || mapEnt->mapEntityNum == 0 ) {
1768                 //%     Sys_Printf( "World:  %d\n", mapEnt->mapEntityNum );
1769                 castShadows = WORLDSPAWN_CAST_SHADOWS;
1770                 recvShadows = WORLDSPAWN_RECV_SHADOWS;
1771         }
1772
1773         /* other entities don't cast any shadows, but recv worldspawn shadows */
1774         else
1775         {
1776                 //%     Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum );
1777                 castShadows = ENTITY_CAST_SHADOWS;
1778                 recvShadows = ENTITY_RECV_SHADOWS;
1779         }
1780
1781         /* get explicit shadow flags */
1782         GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows );
1783
1784         /* vortex: added _ls key (short name of lightmapscale) */
1785         /* ydnar: get lightmap scaling value for this entity */
1786         lightmapScale = 0.0f;
1787         if ( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) ||
1788                  strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) ||
1789                  strcmp( "", ValueForKey( mapEnt, "_ls" ) ) ) {
1790                 /* get lightmap scale from entity */
1791                 lightmapScale = FloatForKey( mapEnt, "lightmapscale" );
1792                 if ( lightmapScale <= 0.0f ) {
1793                         lightmapScale = FloatForKey( mapEnt, "_lightmapscale" );
1794                 }
1795                 if ( lightmapScale <= 0.0f ) {
1796                         lightmapScale = FloatForKey( mapEnt, "_ls" );
1797                 }
1798                 if ( lightmapScale < 0.0f ) {
1799                         lightmapScale = 0.0f;
1800                 }
1801                 if ( lightmapScale > 0.0f ) {
1802                         Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale );
1803                 }
1804         }
1805
1806         /* ydnar: get cel shader :) for this entity */
1807         value = ValueForKey( mapEnt, "_celshader" );
1808         if ( value[ 0 ] == '\0' ) {
1809                 value = ValueForKey( &entities[ 0 ], "_celshader" );
1810         }
1811         if ( value[ 0 ] != '\0' ) {
1812                 if ( strcmp( value, "none" ) ) {
1813                         sprintf( shader, "textures/%s", value );
1814                         celShader = ShaderInfoForShader( shader );
1815                         Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader );
1816                 }
1817                 else
1818                 {
1819                         celShader = NULL;
1820                 }
1821         }
1822         else{
1823                 celShader = ( *globalCelShader ? ShaderInfoForShader( globalCelShader ) : NULL );
1824         }
1825
1826         /* jal : entity based _shadeangle */
1827         shadeAngle = 0.0f;
1828         if ( strcmp( "", ValueForKey( mapEnt, "_shadeangle" ) ) ) {
1829                 shadeAngle = FloatForKey( mapEnt, "_shadeangle" );
1830         }
1831         /* vortex' aliases */
1832         else if ( strcmp( "", ValueForKey( mapEnt, "_smoothnormals" ) ) ) {
1833                 shadeAngle = FloatForKey( mapEnt, "_smoothnormals" );
1834         }
1835         else if ( strcmp( "", ValueForKey( mapEnt, "_sn" ) ) ) {
1836                 shadeAngle = FloatForKey( mapEnt, "_sn" );
1837         }
1838         else if ( strcmp( "", ValueForKey( mapEnt, "_sa" ) ) ) {
1839                 shadeAngle = FloatForKey( mapEnt, "_sa" );
1840         }
1841         else if ( strcmp( "", ValueForKey( mapEnt, "_smooth" ) ) ) {
1842                 shadeAngle = FloatForKey( mapEnt, "_smooth" );
1843         }
1844
1845         if ( shadeAngle < 0.0f ) {
1846                 shadeAngle = 0.0f;
1847         }
1848
1849         if ( shadeAngle > 0.0f ) {
1850                 Sys_Printf( "Entity %d (%s) has shading angle of %.4f\n", mapEnt->mapEntityNum, classname, shadeAngle );
1851         }
1852
1853         /* jal : entity based _samplesize */
1854         lightmapSampleSize = 0;
1855         if ( strcmp( "", ValueForKey( mapEnt, "_lightmapsamplesize" ) ) ) {
1856                 lightmapSampleSize = IntForKey( mapEnt, "_lightmapsamplesize" );
1857         }
1858         else if ( strcmp( "", ValueForKey( mapEnt, "_samplesize" ) ) ) {
1859                 lightmapSampleSize = IntForKey( mapEnt, "_samplesize" );
1860         }
1861         else if ( strcmp( "", ValueForKey( mapEnt, "_ss" ) ) ) {
1862                 lightmapSampleSize = IntForKey( mapEnt, "_ss" );
1863         }
1864
1865         if ( lightmapSampleSize < 0 ) {
1866                 lightmapSampleSize = 0;
1867         }
1868
1869         if ( lightmapSampleSize > 0 ) {
1870                 Sys_Printf( "Entity %d (%s) has lightmap sample size of %d\n", mapEnt->mapEntityNum, classname, lightmapSampleSize );
1871         }
1872
1873         /* attach stuff to everything in the entity */
1874         for ( brush = mapEnt->brushes; brush != NULL; brush = brush->next )
1875         {
1876                 brush->entityNum = mapEnt->mapEntityNum;
1877                 brush->castShadows = castShadows;
1878                 brush->recvShadows = recvShadows;
1879                 brush->lightmapSampleSize = lightmapSampleSize;
1880                 brush->lightmapScale = lightmapScale;
1881                 brush->celShader = celShader;
1882                 brush->shadeAngleDegrees = shadeAngle;
1883         }
1884
1885         for ( patch = mapEnt->patches; patch != NULL; patch = patch->next )
1886         {
1887                 patch->entityNum = mapEnt->mapEntityNum;
1888                 patch->castShadows = castShadows;
1889                 patch->recvShadows = recvShadows;
1890                 patch->lightmapSampleSize = lightmapSampleSize;
1891                 patch->lightmapScale = lightmapScale;
1892                 patch->celShader = celShader;
1893         }
1894
1895         /* ydnar: gs mods: set entity bounds */
1896         SetEntityBounds( mapEnt );
1897
1898         /* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */
1899         LoadEntityIndexMap( mapEnt );
1900
1901         /* get entity origin and adjust brushes */
1902         GetVectorForKey( mapEnt, "origin", mapEnt->origin );
1903         if ( mapEnt->originbrush_origin[ 0 ] || mapEnt->originbrush_origin[ 1 ] || mapEnt->originbrush_origin[ 2 ] ) {
1904                 AdjustBrushesForOrigin( mapEnt );
1905         }
1906
1907         /* group_info entities are just for editor grouping (fixme: leak!) */
1908         if ( !noCollapseGroups && !Q_stricmp( "group_info", classname ) ) {
1909                 numEntities--;
1910                 return qtrue;
1911         }
1912
1913         /* group entities are just for editor convenience, toss all brushes into worldspawn */
1914         if ( !noCollapseGroups && funcGroup ) {
1915                 MoveBrushesToWorld( mapEnt );
1916                 numEntities--;
1917                 return qtrue;
1918         }
1919
1920         /* done */
1921         return qtrue;
1922 }
1923
1924
1925
1926 /*
1927    LoadMapFile()
1928    loads a map file into a list of entities
1929  */
1930
1931 void LoadMapFile( char *filename, qboolean onlyLights, qboolean noCollapseGroups ){
1932         FILE        *file;
1933         brush_t     *b;
1934         int oldNumEntities = 0, numMapBrushes;
1935
1936
1937         /* note it */
1938         Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" );
1939         Sys_Printf( "Loading %s\n", filename );
1940
1941         /* hack */
1942         file = SafeOpenRead( filename );
1943         fclose( file );
1944
1945         /* load the map file */
1946         LoadScriptFile( filename, -1 );
1947
1948         /* setup */
1949         if ( onlyLights ) {
1950                 oldNumEntities = numEntities;
1951         }
1952         else{
1953                 numEntities = 0;
1954         }
1955
1956         /* initial setup */
1957         numMapDrawSurfs = 0;
1958         c_detail = 0;
1959         g_bBrushPrimit = BPRIMIT_UNDEFINED;
1960
1961         /* allocate a very large temporary brush for building the brushes as they are loaded */
1962         buildBrush = AllocBrush( MAX_BUILD_SIDES );
1963
1964         /* parse the map file */
1965         while ( ParseMapEntity( onlyLights, noCollapseGroups ) ) ;
1966
1967         /* light loading */
1968         if ( onlyLights ) {
1969                 /* emit some statistics */
1970                 Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities );
1971         }
1972         else
1973         {
1974                 /* set map bounds */
1975                 ClearBounds( mapMins, mapMaxs );
1976                 for ( b = entities[ 0 ].brushes; b; b = b->next )
1977                 {
1978                         AddPointToBounds( b->mins, mapMins, mapMaxs );
1979                         AddPointToBounds( b->maxs, mapMins, mapMaxs );
1980                 }
1981
1982                 /* get brush counts */
1983                 numMapBrushes = CountBrushList( entities[ 0 ].brushes );
1984                 if ( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 ) {
1985                         Sys_FPrintf( SYS_WRN, "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" );
1986                 }
1987
1988                 /* emit some statistics */
1989                 Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes );
1990                 Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail );
1991                 Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches );
1992                 Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels );
1993                 Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels );
1994                 Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities );
1995                 Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes );
1996                 Sys_Printf( "%9d areaportals\n", c_areaportals );
1997                 Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n",
1998                                         mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ],
1999                                         mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ] );
2000
2001                 /* write bogus map */
2002                 if ( fakemap ) {
2003                         WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes );
2004                 }
2005         }
2006 }