]> de.git.xonotic.org Git - voretournament/voretournament.git/blob - misc/mediasource/extra/netradiant-src/tools/quake3/q3map2/fog.c
Include netRadiant source in this GIT
[voretournament/voretournament.git] / misc / mediasource / extra / netradiant-src / tools / quake3 / q3map2 / fog.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 FOG_C
33
34
35
36 /* dependencies */
37 #include "q3map2.h"
38
39
40
41 int                     numFogFragments;
42 int                     numFogPatchFragments;
43
44
45
46 /*
47 DrawSurfToMesh()
48 converts a patch drawsurface to a mesh_t
49 */
50
51 mesh_t *DrawSurfToMesh( mapDrawSurface_t *ds )
52 {
53         mesh_t          *m;
54         
55         
56         m = safe_malloc( sizeof( *m ) );
57         m->width = ds->patchWidth;
58         m->height = ds->patchHeight;
59         m->verts = safe_malloc( sizeof(m->verts[ 0 ]) * m->width * m->height );
60         memcpy( m->verts, ds->verts, sizeof(m->verts[ 0 ]) * m->width * m->height );
61         
62         return m;
63 }
64
65
66
67 /*
68 SplitMeshByPlane()
69 chops a mesh by a plane
70 */
71
72 void SplitMeshByPlane( mesh_t *in, vec3_t normal, float dist, mesh_t **front, mesh_t **back )
73 {
74         int             w, h, split;
75         float   d[MAX_PATCH_SIZE][MAX_PATCH_SIZE];
76         bspDrawVert_t   *dv, *v1, *v2;
77         int             c_front, c_back, c_on;
78         mesh_t  *f, *b;
79         int             i;
80         float   frac;
81         int             frontAprox, backAprox;
82
83         for ( i = 0 ; i < 2 ; i++ ) {
84                 dv = in->verts;
85                 c_front = 0;
86                 c_back = 0;
87                 c_on = 0;
88                 for ( h = 0 ; h < in->height ; h++ ) {
89                         for ( w = 0 ; w < in->width ; w++, dv++ ) {
90                                 d[h][w] = DotProduct( dv->xyz, normal ) - dist;
91                                 if ( d[h][w] > ON_EPSILON ) {
92                                         c_front++;
93                                 } else if ( d[h][w] < -ON_EPSILON ) {
94                                         c_back++;
95                                 } else {
96                                         c_on++;
97                                 }
98                         }
99                 }
100
101                 *front = NULL;
102                 *back = NULL;
103
104                 if ( !c_front ) {
105                         *back = in;
106                         return;
107                 }
108                 if ( !c_back ) {
109                         *front = in;
110                         return;
111                 }
112
113                 // find a split point
114                 split = -1;
115                 for ( w = 0 ; w < in->width -1 ; w++ ) {
116                         if ( ( d[0][w] < 0 ) != ( d[0][w+1] < 0 ) ) {
117                                 if ( split == -1 ) {
118                                         split = w;
119                                         break;
120                                 }
121                         }
122                 }
123
124                 if ( split == -1 ) {
125                         if ( i == 1 ) {
126                                 Sys_FPrintf (SYS_VRB, "No crossing points in patch\n");
127                                 *front = in;
128                                 return;
129                         }
130
131                         in = TransposeMesh( in );
132                         InvertMesh( in );
133                         continue;
134                 }
135
136                 // make sure the split point stays the same for all other rows
137                 for ( h = 1 ; h < in->height ; h++ ) {
138                         for ( w = 0 ; w < in->width -1 ; w++ ) {
139                                 if ( ( d[h][w] < 0 ) != ( d[h][w+1] < 0 ) ) {
140                                         if ( w != split ) {
141                                                 Sys_Printf( "multiple crossing points for patch -- can't clip\n");
142                                                 *front = in;
143                                                 return;
144                                         }
145                                 }
146                         }
147                         if ( ( d[h][split] < 0 ) == ( d[h][split+1] < 0 ) ) {
148                                 Sys_Printf( "differing crossing points for patch -- can't clip\n");
149                                 *front = in;
150                                 return;
151                         }
152                 }
153
154                 break;
155         }
156
157
158         // create two new meshes
159         f = safe_malloc( sizeof( *f ) );
160         f->width = split + 2;
161         if ( ! (f->width & 1) ) {
162                 f->width++;
163                 frontAprox = 1;
164         } else {
165                 frontAprox = 0;
166         }
167         if ( f->width > MAX_PATCH_SIZE ) {
168                 Error( "MAX_PATCH_SIZE after split");
169         }
170         f->height = in->height;
171         f->verts = safe_malloc( sizeof(f->verts[0]) * f->width * f->height );
172
173         b = safe_malloc( sizeof( *b ) );
174         b->width = in->width - split;
175         if ( ! (b->width & 1) ) {
176                 b->width++;
177                 backAprox = 1;
178         } else {
179                 backAprox = 0;
180         }
181         if ( b->width > MAX_PATCH_SIZE ) {
182                 Error( "MAX_PATCH_SIZE after split");
183         }
184         b->height = in->height;
185         b->verts = safe_malloc( sizeof(b->verts[0]) * b->width * b->height );
186
187         if ( d[0][0] > 0 ) {
188                 *front = f;
189                 *back = b;
190         } else {
191                 *front = b;
192                 *back = f;
193         }
194
195         // distribute the points
196         for ( w = 0 ; w < in->width ; w++ ) {
197                 for ( h = 0 ; h < in->height ; h++ ) {
198                         if ( w <= split ) {
199                                 f->verts[ h * f->width + w ] = in->verts[ h * in->width + w ];
200                         } else {
201                                 b->verts[ h * b->width + w - split + backAprox ] = in->verts[ h * in->width + w ];
202                         }
203                 }
204         }
205
206         // clip the crossing line
207         for ( h = 0; h < in->height; h++ )
208         {
209                 dv = &f->verts[ h * f->width + split + 1 ];
210                 v1 = &in->verts[ h * in->width + split ];
211                 v2 = &in->verts[ h * in->width + split + 1 ];
212
213                 frac = d[h][split] / ( d[h][split] - d[h][split+1] );
214                 
215                 /* interpolate */
216                 //%     for( i = 0; i < 10; i++ )
217                 //%             dv->xyz[ i ] = v1->xyz[ i ] + frac * (v2->xyz[ i ] - v1->xyz[ i ]);
218                 //%     dv->xyz[10] = 0;        // set all 4 colors to 0 
219                 LerpDrawVertAmount( v1, v2, frac, dv );
220                 
221                 if ( frontAprox ) {
222                         f->verts[ h * f->width + split + 2 ] = *dv;
223                 }
224                 b->verts[ h * b->width ] = *dv;
225                 if ( backAprox ) {
226                         b->verts[ h * b->width + 1 ] = *dv;
227                 }
228         }
229
230         /*
231 PrintMesh( in );
232 Sys_Printf("\n");
233 PrintMesh( f );
234 Sys_Printf("\n");
235 PrintMesh( b );
236 Sys_Printf("\n");
237         */
238
239         FreeMesh( in );
240 }
241
242
243 /*
244 ChopPatchSurfaceByBrush()
245 chops a patch up by a fog brush
246 */
247
248 qboolean ChopPatchSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b )
249 {
250         int                     i, j;
251         side_t          *s;
252         plane_t         *plane;
253         mesh_t          *outside[MAX_BRUSH_SIDES];
254         int                     numOutside;
255         mesh_t          *m, *front, *back;
256         mapDrawSurface_t        *newds;
257
258         m = DrawSurfToMesh( ds );
259         numOutside = 0;
260         
261         // only split by the top and bottom planes to avoid
262         // some messy patch clipping issues
263         
264         for ( i = 4 ; i <= 5 ; i++ ) {
265                 s = &b->sides[ i ];
266                 plane = &mapplanes[ s->planenum ];
267
268                 SplitMeshByPlane( m, plane->normal, plane->dist, &front, &back );
269
270                 if ( !back ) {
271                         // nothing actually contained inside
272                         for ( j = 0 ; j < numOutside ; j++ ) {
273                                 FreeMesh( outside[j] );
274                         }
275                         return qfalse;
276                 }
277                 m = back;
278
279                 if ( front ) {
280                         if ( numOutside == MAX_BRUSH_SIDES ) {
281                                 Error( "MAX_BRUSH_SIDES" );
282                         }
283                         outside[ numOutside ] = front;
284                         numOutside++;
285                 }
286         }
287
288         /* all of outside fragments become seperate drawsurfs */
289         numFogPatchFragments += numOutside;
290         for( i = 0; i < numOutside; i++ )
291         {
292                 /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */
293                 outside[ i ] = TransposeMesh( outside[ i ] );
294                 InvertMesh( outside[ i ] );
295                 
296                 /* ydnar: do this the hacky right way */
297                 newds = AllocDrawSurface( SURFACE_PATCH );
298                 memcpy( newds, ds, sizeof( *ds ) );
299                 newds->patchWidth = outside[ i ]->width;
300                 newds->patchHeight = outside[ i ]->height;
301                 newds->numVerts = outside[ i ]->width * outside[ i ]->height;
302                 newds->verts = safe_malloc( newds->numVerts * sizeof( *newds->verts ) );
303                 memcpy( newds->verts, outside[ i ]->verts, newds->numVerts * sizeof( *newds->verts ) );
304                 
305                 /* free the source mesh */
306                 FreeMesh( outside[ i ] );
307         }
308         
309         /* only rejigger this patch if it was chopped */
310         //%     Sys_Printf( "Inside: %d x %d\n", m->width, m->height );
311         if( numOutside > 0 )
312         {
313                 /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */
314                 m = TransposeMesh( m );
315                 InvertMesh( m );
316                 
317                 /* replace ds with m */
318                 ds->patchWidth = m->width;
319                 ds->patchHeight = m->height;
320                 ds->numVerts = m->width * m->height;
321                 free( ds->verts );
322                 ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
323                 memcpy( ds->verts, m->verts, ds->numVerts * sizeof( *ds->verts ) );
324         }
325         
326         /* free the source mesh and return */
327         FreeMesh( m );
328         return qtrue;
329 }
330
331
332
333 /*
334 WindingFromDrawSurf()
335 creates a winding from a surface's verts
336 */
337
338 winding_t *WindingFromDrawSurf( mapDrawSurface_t *ds )
339 {
340         winding_t       *w;
341         int                     i;
342
343         // we use the first point of the surface, maybe something more clever would be useful
344         // (actually send the whole draw surface would be cool?)
345         if( ds->numVerts >= MAX_POINTS_ON_WINDING )
346         {
347                 int max = ds->numVerts;
348                 vec3_t p[256];
349
350                 if(max > 256)
351                         max = 256;
352
353                 for ( i = 0 ; i < max ; i++ ) {
354                         VectorCopy( ds->verts[i].xyz, p[i] );
355                 }
356
357                 xml_Winding( "WindingFromDrawSurf failed: MAX_POINTS_ON_WINDING exceeded", p, max, qtrue );
358         }
359
360         w = AllocWinding( ds->numVerts );
361         w->numpoints = ds->numVerts;
362         for ( i = 0 ; i < ds->numVerts ; i++ ) {
363                 VectorCopy( ds->verts[i].xyz, w->p[i] );
364         }
365         return w;
366 }
367
368
369
370 /*
371 ChopFaceSurfaceByBrush()
372 chops up a face drawsurface by a fog brush, with a potential fragment left inside
373 */
374
375 qboolean ChopFaceSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b )
376 {
377         int                                     i, j;
378         side_t                          *s;
379         plane_t                         *plane;
380         winding_t                       *w;
381         winding_t                       *front, *back;
382         winding_t                       *outside[ MAX_BRUSH_SIDES ];
383         int                                     numOutside;
384         mapDrawSurface_t        *newds;
385         
386         
387         /* dummy check */
388         if( ds->sideRef == NULL || ds->sideRef->side == NULL )
389                 return qfalse;
390         
391         /* initial setup */
392         w = WindingFromDrawSurf( ds );
393         numOutside = 0;
394         
395         /* chop by each brush side */
396         for( i = 0; i < b->numsides; i++ )
397         {
398                 /* get brush side and plane */
399                 s = &b->sides[ i ];
400                 plane = &mapplanes[ s->planenum ];
401                 
402                 /* handle coplanar outfacing (don't fog) */
403                 if( ds->sideRef->side->planenum == s->planenum )
404                         return qfalse;
405                 
406                 /* handle coplanar infacing (keep inside) */
407                 if( (ds->sideRef->side->planenum ^ 1) == s->planenum )
408                         continue;
409                 
410                 /* general case */
411                 ClipWindingEpsilon( w, plane->normal, plane->dist, ON_EPSILON, &front, &back );
412                 FreeWinding( w );
413                 
414                 if( back == NULL )
415                 {
416                         /* nothing actually contained inside */
417                         for( j = 0; j < numOutside; j++ )
418                                 FreeWinding( outside[ j ] );
419                         return qfalse;
420                 }
421                 
422                 if( front != NULL )
423                 {
424                         if( numOutside == MAX_BRUSH_SIDES )
425                                 Error( "MAX_BRUSH_SIDES" );
426                         outside[ numOutside ] = front;
427                         numOutside++;
428                 }
429                 
430                 w = back;
431         }
432         
433         /* fixme: celshaded surface fragment errata */
434         
435         /* all of outside fragments become seperate drawsurfs */
436         numFogFragments += numOutside;
437         s = ds->sideRef->side;
438         for( i = 0; i < numOutside; i++ )
439         {
440                 newds = DrawSurfaceForSide( e, ds->mapBrush, s, outside[ i ] );
441                 newds->fogNum = ds->fogNum;
442                 FreeWinding( outside[ i ] );
443         }
444         
445         /* ydnar: the old code neglected to snap to 0.125 for the fragment
446                   inside the fog brush, leading to sparklies. this new code does
447                           the right thing and uses the original surface's brush side */
448         
449         /* build a drawsurf for it */
450         newds = DrawSurfaceForSide( e, ds->mapBrush, s, w );
451         if( newds == NULL )
452                 return qfalse;
453         
454         /* copy new to original */
455         ClearSurface( ds );
456         memcpy( ds, newds, sizeof( mapDrawSurface_t ) );
457         
458         /* didn't really add a new drawsurface... :) */
459         numMapDrawSurfs--;
460         
461         /* return ok */
462         return qtrue;
463 }
464
465
466
467 /*
468 FogDrawSurfaces()
469 call after the surface list has been pruned, before tjunction fixing
470 */
471
472 void FogDrawSurfaces( entity_t *e )
473 {
474         int                                     i, j, k, fogNum;
475         fog_t                           *fog;
476         mapDrawSurface_t        *ds;
477         vec3_t                          mins, maxs;
478         int                                     fogged, numFogged;
479         int                                     numBaseDrawSurfs;
480         
481         
482         /* note it */
483         Sys_FPrintf( SYS_VRB, "----- FogDrawSurfs -----\n" );
484         
485         /* reset counters */
486         numFogged = 0;
487         numFogFragments = 0;
488         
489         /* walk fog list */
490         for( fogNum = 0; fogNum < numMapFogs; fogNum++ )
491         {
492                 /* get fog */
493                 fog = &mapFogs[ fogNum ];
494                 
495                 /* clip each surface into this, but don't clip any of the resulting fragments to the same brush */
496                 numBaseDrawSurfs = numMapDrawSurfs;
497                 for( i = 0; i < numBaseDrawSurfs; i++ )
498                 {
499                         /* get the drawsurface */
500                         ds = &mapDrawSurfs[ i ];
501                         
502                         /* no fog? */
503                         if( ds->shaderInfo->noFog )
504                                 continue;
505                         
506                         /* global fog doesn't have a brush */
507                         if( fog->brush == NULL )
508                         {
509                                 /* don't re-fog already fogged surfaces */
510                                 if( ds->fogNum >= 0 )
511                                         continue;
512                                 fogged = 1;
513                         }
514                         else
515                         {
516                                 /* find drawsurface bounds */
517                                 ClearBounds( mins, maxs );
518                                 for( j = 0; j < ds->numVerts; j++ )
519                                         AddPointToBounds( ds->verts[ j ].xyz, mins, maxs );
520
521                                 /* check against the fog brush */
522                                 for( k = 0; k < 3; k++ )
523                                 {
524                                         if( mins[ k ] > fog->brush->maxs[ k ] )
525                                                 break;
526                                         if( maxs[ k ] < fog->brush->mins[ k ] )
527                                                 break;
528                                 }
529                                 
530                                 /* no intersection? */
531                                 if( k < 3 )
532                                         continue;
533                                 
534                                 /* ydnar: gs mods: handle the various types of surfaces */
535                                 switch( ds->type )
536                                 {
537                                         /* handle brush faces */
538                                         case SURFACE_FACE:
539                                                 fogged = ChopFaceSurfaceByBrush( e, ds, fog->brush );
540                                                 break;
541                                         
542                                         /* handle patches */
543                                         case SURFACE_PATCH:
544                                                 fogged = ChopPatchSurfaceByBrush( e, ds, fog->brush );
545                                                 break;
546                                         
547                                         /* handle triangle surfaces (fixme: split triangle surfaces) */
548                                         case SURFACE_TRIANGLES:
549                                         case SURFACE_FORCED_META:
550                                         case SURFACE_META:
551                                                 fogged = 1;
552                                                 break;
553
554                                         /* no fogging */
555                                         default:
556                                                 fogged = 0;
557                                                 break;
558                                 }
559                         }
560                         
561                         /* is this surface fogged? */
562                         if( fogged )
563                         {
564                                 numFogged += fogged;
565                                 ds->fogNum = fogNum;
566                         }
567                 }
568         }
569         
570         /* emit some statistics */
571         Sys_FPrintf( SYS_VRB, "%9d fog polygon fragments\n", numFogFragments );
572         Sys_FPrintf( SYS_VRB, "%9d fog patch fragments\n", numFogPatchFragments );
573         Sys_FPrintf( SYS_VRB, "%9d fogged drawsurfs\n", numFogged );
574 }
575
576
577
578 /*
579 FogForPoint() - ydnar
580 gets the fog number for a point in space
581 */
582
583 int FogForPoint( vec3_t point, float epsilon )
584 {
585         int                             fogNum, i, j;
586         float                   dot;
587         qboolean                inside;
588         brush_t                 *brush;
589         plane_t                 *plane;
590         
591         
592         /* start with bogus fog num */
593         fogNum = defaultFogNum;
594         
595         /* walk the list of fog volumes */
596         for( i = 0; i < numMapFogs; i++ )
597         {
598                 /* sof2: global fog doesn't reference a brush */
599                 if( mapFogs[ i ].brush == NULL )
600                 {
601                         fogNum = i;
602                         continue;
603                 }
604                 
605                 /* get fog brush */
606                 brush = mapFogs[ i ].brush;
607                 
608                 /* check point against all planes */
609                 inside = qtrue;
610                 for( j = 0; j < brush->numsides && inside; j++ )
611                 {
612                         plane = &mapplanes[ brush->sides[ j ].planenum ];       /* note usage of map planes here */
613                         dot = DotProduct( point, plane->normal );
614                         dot -= plane->dist;
615                         if( dot > epsilon )
616                                 inside = qfalse;
617                 }
618                 
619                 /* if inside, return the fog num */
620                 if( inside )
621                 {
622                         //%     Sys_Printf( "FogForPoint: %f, %f, %f in fog %d\n", point[ 0 ], point[ 1 ], point[ 2 ], i );
623                         return i;
624                 }
625         }
626         
627         /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */
628         return fogNum;
629 }
630
631
632
633 /*
634 FogForBounds() - ydnar
635 gets the fog number for a bounding box
636 */
637
638 int FogForBounds( vec3_t mins, vec3_t maxs, float epsilon )
639 {
640         int                             fogNum, i, j;
641         float                   highMin, lowMax, volume, bestVolume;
642         vec3_t                  fogMins, fogMaxs, overlap;
643         brush_t                 *brush;
644         
645         
646         /* start with bogus fog num */
647         fogNum = defaultFogNum;
648         
649         /* init */
650         bestVolume = 0.0f;
651         
652         /* walk the list of fog volumes */
653         for( i = 0; i < numMapFogs; i++ )
654         {
655                 /* sof2: global fog doesn't reference a brush */
656                 if( mapFogs[ i ].brush == NULL )
657                 {
658                         fogNum = i;
659                         continue;
660                 }
661                 
662                 /* get fog brush */
663                 brush = mapFogs[ i ].brush;
664                 
665                 /* get bounds */
666                 fogMins[ 0 ] = brush->mins[ 0 ] - epsilon;
667                 fogMins[ 1 ] = brush->mins[ 1 ] - epsilon;
668                 fogMins[ 2 ] = brush->mins[ 2 ] - epsilon;
669                 fogMaxs[ 0 ] = brush->maxs[ 0 ] + epsilon;
670                 fogMaxs[ 1 ] = brush->maxs[ 1 ] + epsilon;
671                 fogMaxs[ 2 ] = brush->maxs[ 2 ] + epsilon;
672                 
673                 /* check against bounds */
674                 for( j = 0; j < 3; j++ )
675                 {
676                         if( mins[ j ] > fogMaxs[ j ] || maxs[ j ] < fogMins[ j ] )
677                                 break;
678                         highMin = mins[ j ] > fogMins[ j ] ? mins[ j ] : fogMins[ j ];
679                         lowMax = maxs[ j ] < fogMaxs[ j ] ? maxs[ j ] : fogMaxs[ j ];
680                         overlap[ j ] = lowMax - highMin;
681                         if( overlap[ j ] < 1.0f )
682                                 overlap[ j ] = 1.0f;
683                 }
684                 
685                 /* no overlap */
686                 if( j < 3 )
687                         continue;
688                 
689                 /* get volume */
690                 volume = overlap[ 0 ] * overlap[ 1 ] * overlap[ 2 ];
691                 
692                 /* test against best volume */
693                 if( volume > bestVolume )
694                 {
695                         bestVolume = volume;
696                         fogNum = i;
697                 }
698         }
699         
700         /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */
701         return fogNum;
702 }
703
704
705
706 /*
707 CreateMapFogs() - ydnar
708 generates a list of map fogs
709 */
710
711 void CreateMapFogs( void )
712 {
713         int                     i;
714         entity_t        *entity;
715         brush_t         *brush;
716         fog_t           *fog;
717         vec3_t          invFogDir;
718         const char      *globalFog;
719         
720         
721         /* skip? */
722         if( nofog )
723                 return;
724         
725         /* note it */
726         Sys_FPrintf( SYS_VRB, "--- CreateMapFogs ---\n" );
727         
728         /* walk entities */
729         for( i = 0; i < numEntities; i++ )
730         {
731                 /* get entity */
732                 entity = &entities[ i ];
733                 
734                 /* walk entity brushes */
735                 for( brush = entity->brushes; brush != NULL; brush = brush->next )
736                 {
737                         /* ignore non-fog brushes */
738                         if( brush->contentShader->fogParms == qfalse )
739                                 continue;
740                         
741                         /* test limit */
742                         if( numMapFogs >= MAX_MAP_FOGS )
743                                 Error( "Exceeded MAX_MAP_FOGS (%d)", MAX_MAP_FOGS );
744                         
745                         /* set up fog */
746                         fog = &mapFogs[ numMapFogs++ ];
747                         fog->si = brush->contentShader;
748                         fog->brush = brush;
749                         fog->visibleSide = -1;
750                         
751                         /* if shader specifies an explicit direction, then find a matching brush side with an opposed normal */
752                         if( VectorLength( fog->si->fogDir ) )
753                         {
754                                 /* flip it */
755                                 VectorScale( fog->si->fogDir, -1.0f, invFogDir );
756                                 
757                                 /* find the brush side */
758                                 for( i = 0; i < brush->numsides; i++ )
759                                 {
760                                         if( VectorCompare( invFogDir, mapplanes[ brush->sides[ i ].planenum ].normal ) )
761                                         {
762                                                 fog->visibleSide = i;
763                                                 //%     Sys_Printf( "Brush num: %d Side num: %d\n", fog->brushNum, fog->visibleSide );
764                                                 break;
765                                         }
766                                 }
767                         }
768                 }
769         }
770         
771         /* ydnar: global fog */
772         globalFog = ValueForKey( &entities[ 0 ], "_fog" );
773         if( globalFog[ 0 ] == '\0' )
774                 globalFog = ValueForKey( &entities[ 0 ], "fog" );
775         if( globalFog[ 0 ] != '\0' )
776         {
777                 /* test limit */
778                 if( numMapFogs >= MAX_MAP_FOGS )
779                         Error( "Exceeded MAX_MAP_FOGS (%d) trying to add global fog", MAX_MAP_FOGS );
780                 
781                 /* note it */
782                 Sys_FPrintf( SYS_VRB, "Map has global fog shader %s\n", globalFog );
783                 
784                 /* set up fog */
785                 fog = &mapFogs[ numMapFogs++ ];
786                 fog->si = ShaderInfoForShader( globalFog );
787                 if( fog->si == NULL )
788                         Error( "Invalid shader \"%s\" referenced trying to add global fog", globalFog );
789                 fog->brush = NULL;
790                 fog->visibleSide = -1;
791                 
792                 /* set as default fog */
793                 defaultFogNum = numMapFogs - 1;
794                 
795                 /* mark all worldspawn brushes as fogged */
796                 for( brush = entities[ 0 ].brushes; brush != NULL; brush = brush->next )
797                         ApplySurfaceParm( "fog", &brush->contentFlags, NULL, &brush->compileFlags );
798         }
799         
800         /* emit some stats */
801         Sys_FPrintf( SYS_VRB, "%9d fogs\n", numMapFogs );
802 }
803