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