1 /* -------------------------------------------------------------------------------
2
3 Copyright (C) 1999-2006 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
DrawSurfToMesh(mapDrawSurface_t * ds)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
SplitMeshByPlane(mesh_t * in,vec3_t normal,float dist,mesh_t ** front,mesh_t ** back)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
ChopPatchSurfaceByBrush(entity_t * e,mapDrawSurface_t * ds,brush_t * b)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
WindingFromDrawSurf(mapDrawSurface_t * ds)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
ChopFaceSurfaceByBrush(entity_t * e,mapDrawSurface_t * ds,brush_t * b)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
FogDrawSurfaces(entity_t * e)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
FogForPoint(vec3_t point,float epsilon)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
FogForBounds(vec3_t mins,vec3_t maxs,float epsilon)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
CreateMapFogs(void)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
804