]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/minimap.c
split bsp conversion stuff from q3map2
[xonotic/netradiant.git] / tools / quake3 / q3map2 / minimap.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 /* dependencies */
32 #include "q3map2.h"
33
34 /* minimap stuff */
35
36 typedef struct minimap_s
37 {
38         bspModel_t *model;
39         int width;
40         int height;
41         int samples;
42         float *sample_offsets;
43         float sharpen_boxmult;
44         float sharpen_centermult;
45         float boost, brightness, contrast;
46         float *data1f;
47         float *sharpendata1f;
48         vec3_t mins, size;
49 }
50 minimap_t;
51
52 static minimap_t minimap;
53
54 qboolean BrushIntersectionWithLine( bspBrush_t *brush, vec3_t start, vec3_t dir, float *t_in, float *t_out ){
55         int i;
56         qboolean in = qfalse, out = qfalse;
57         bspBrushSide_t *sides = &bspBrushSides[brush->firstSide];
58
59         for ( i = 0; i < brush->numSides; ++i )
60         {
61                 bspPlane_t *p = &bspPlanes[sides[i].planeNum];
62                 float sn = DotProduct( start, p->normal );
63                 float dn = DotProduct( dir, p->normal );
64                 if ( dn == 0 ) {
65                         if ( sn > p->dist ) {
66                                 return qfalse; // outside!
67                         }
68                 }
69                 else
70                 {
71                         float t = ( p->dist - sn ) / dn;
72                         if ( dn < 0 ) {
73                                 if ( !in || t > *t_in ) {
74                                         *t_in = t;
75                                         in = qtrue;
76                                         // as t_in can only increase, and t_out can only decrease, early out
77                                         if ( out && *t_in >= *t_out ) {
78                                                 return qfalse;
79                                         }
80                                 }
81                         }
82                         else
83                         {
84                                 if ( !out || t < *t_out ) {
85                                         *t_out = t;
86                                         out = qtrue;
87                                         // as t_in can only increase, and t_out can only decrease, early out
88                                         if ( in && *t_in >= *t_out ) {
89                                                 return qfalse;
90                                         }
91                                 }
92                         }
93                 }
94         }
95         return in && out;
96 }
97
98 static float MiniMapSample( float x, float y ){
99         vec3_t org, dir;
100         int i, bi;
101         float t0, t1;
102         float samp;
103         bspBrush_t *b;
104         bspBrushSide_t *s;
105         int cnt;
106
107         org[0] = x;
108         org[1] = y;
109         org[2] = 0;
110         dir[0] = 0;
111         dir[1] = 0;
112         dir[2] = 1;
113
114         cnt = 0;
115         samp = 0;
116         for ( i = 0; i < minimap.model->numBSPBrushes; ++i )
117         {
118                 bi = minimap.model->firstBSPBrush + i;
119                 if ( opaqueBrushes[bi >> 3] & ( 1 << ( bi & 7 ) ) ) {
120                         b = &bspBrushes[bi];
121
122                         // sort out mins/maxs of the brush
123                         s = &bspBrushSides[b->firstSide];
124                         if ( x < -bspPlanes[s[0].planeNum].dist ) {
125                                 continue;
126                         }
127                         if ( x > +bspPlanes[s[1].planeNum].dist ) {
128                                 continue;
129                         }
130                         if ( y < -bspPlanes[s[2].planeNum].dist ) {
131                                 continue;
132                         }
133                         if ( y > +bspPlanes[s[3].planeNum].dist ) {
134                                 continue;
135                         }
136
137                         if ( BrushIntersectionWithLine( b, org, dir, &t0, &t1 ) ) {
138                                 samp += t1 - t0;
139                                 ++cnt;
140                         }
141                 }
142         }
143
144         return samp;
145 }
146
147 void RandomVector2f( float v[2] ){
148         do
149         {
150                 v[0] = 2 * Random() - 1;
151                 v[1] = 2 * Random() - 1;
152         }
153         while ( v[0] * v[0] + v[1] * v[1] > 1 );
154 }
155
156 static void MiniMapRandomlySupersampled( int y ){
157         int x, i;
158         float *p = &minimap.data1f[y * minimap.width];
159         float ymin = minimap.mins[1] + minimap.size[1] * ( y / (float) minimap.height );
160         float dx   =                   minimap.size[0]      / (float) minimap.width;
161         float dy   =                   minimap.size[1]      / (float) minimap.height;
162         float uv[2];
163         float thisval;
164
165         for ( x = 0; x < minimap.width; ++x )
166         {
167                 float xmin = minimap.mins[0] + minimap.size[0] * ( x / (float) minimap.width );
168                 float val = 0;
169
170                 for ( i = 0; i < minimap.samples; ++i )
171                 {
172                         RandomVector2f( uv );
173                         thisval = MiniMapSample(
174                                 xmin + ( uv[0] + 0.5 ) * dx, /* exaggerated random pattern for better results */
175                                 ymin + ( uv[1] + 0.5 ) * dy  /* exaggerated random pattern for better results */
176                                 );
177                         val += thisval;
178                 }
179                 val /= minimap.samples * minimap.size[2];
180                 *p++ = val;
181         }
182 }
183
184 static void MiniMapSupersampled( int y ){
185         int x, i;
186         float *p = &minimap.data1f[y * minimap.width];
187         float ymin = minimap.mins[1] + minimap.size[1] * ( y / (float) minimap.height );
188         float dx   =                   minimap.size[0]      / (float) minimap.width;
189         float dy   =                   minimap.size[1]      / (float) minimap.height;
190
191         for ( x = 0; x < minimap.width; ++x )
192         {
193                 float xmin = minimap.mins[0] + minimap.size[0] * ( x / (float) minimap.width );
194                 float val = 0;
195
196                 for ( i = 0; i < minimap.samples; ++i )
197                 {
198                         float thisval = MiniMapSample(
199                                 xmin + minimap.sample_offsets[2 * i + 0] * dx,
200                                 ymin + minimap.sample_offsets[2 * i + 1] * dy
201                                 );
202                         val += thisval;
203                 }
204                 val /= minimap.samples * minimap.size[2];
205                 *p++ = val;
206         }
207 }
208
209 static void MiniMapNoSupersampling( int y ){
210         int x;
211         float *p = &minimap.data1f[y * minimap.width];
212         float ymin = minimap.mins[1] + minimap.size[1] * ( ( y + 0.5 ) / (float) minimap.height );
213
214         for ( x = 0; x < minimap.width; ++x )
215         {
216                 float xmin = minimap.mins[0] + minimap.size[0] * ( ( x + 0.5 ) / (float) minimap.width );
217                 *p++ = MiniMapSample( xmin, ymin ) / minimap.size[2];
218         }
219 }
220
221 static void MiniMapSharpen( int y ){
222         int x;
223         qboolean up = ( y > 0 );
224         qboolean down = ( y < minimap.height - 1 );
225         float *p = &minimap.data1f[y * minimap.width];
226         float *q = &minimap.sharpendata1f[y * minimap.width];
227
228         for ( x = 0; x < minimap.width; ++x )
229         {
230                 qboolean left = ( x > 0 );
231                 qboolean right = ( x < minimap.width - 1 );
232                 float val = p[0] * minimap.sharpen_centermult;
233
234                 if ( left && up ) {
235                         val += p[-1 - minimap.width] * minimap.sharpen_boxmult;
236                 }
237                 if ( left && down ) {
238                         val += p[-1 + minimap.width] * minimap.sharpen_boxmult;
239                 }
240                 if ( right && up ) {
241                         val += p[+1 - minimap.width] * minimap.sharpen_boxmult;
242                 }
243                 if ( right && down ) {
244                         val += p[+1 + minimap.width] * minimap.sharpen_boxmult;
245                 }
246
247                 if ( left ) {
248                         val += p[-1] * minimap.sharpen_boxmult;
249                 }
250                 if ( right ) {
251                         val += p[+1] * minimap.sharpen_boxmult;
252                 }
253                 if ( up ) {
254                         val += p[-minimap.width] * minimap.sharpen_boxmult;
255                 }
256                 if ( down ) {
257                         val += p[+minimap.width] * minimap.sharpen_boxmult;
258                 }
259
260                 ++p;
261                 *q++ = val;
262         }
263 }
264
265 static void MiniMapContrastBoost( int y ){
266         int x;
267         float *q = &minimap.data1f[y * minimap.width];
268         for ( x = 0; x < minimap.width; ++x )
269         {
270                 *q = *q * minimap.boost / ( ( minimap.boost - 1 ) * *q + 1 );
271                 ++q;
272         }
273 }
274
275 static void MiniMapBrightnessContrast( int y ){
276         int x;
277         float *q = &minimap.data1f[y * minimap.width];
278         for ( x = 0; x < minimap.width; ++x )
279         {
280                 *q = *q * minimap.contrast + minimap.brightness;
281                 ++q;
282         }
283 }
284
285 void MiniMapMakeMinsMaxs( vec3_t mins_in, vec3_t maxs_in, float border, qboolean keepaspect ){
286         vec3_t mins, maxs, extend;
287         VectorCopy( mins_in, mins );
288         VectorCopy( maxs_in, maxs );
289
290         // line compatible to nexuiz mapinfo
291         Sys_Printf( "size %f %f %f %f %f %f\n", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2] );
292
293         if ( keepaspect ) {
294                 VectorSubtract( maxs, mins, extend );
295                 if ( extend[1] > extend[0] ) {
296                         mins[0] -= ( extend[1] - extend[0] ) * 0.5;
297                         maxs[0] += ( extend[1] - extend[0] ) * 0.5;
298                 }
299                 else
300                 {
301                         mins[1] -= ( extend[0] - extend[1] ) * 0.5;
302                         maxs[1] += ( extend[0] - extend[1] ) * 0.5;
303                 }
304         }
305
306         /* border: amount of black area around the image */
307         /* input: border, 1-2*border, border but we need border/(1-2*border) */
308
309         VectorSubtract( maxs, mins, extend );
310         VectorScale( extend, border / ( 1 - 2 * border ), extend );
311
312         VectorSubtract( mins, extend, mins );
313         VectorAdd( maxs, extend, maxs );
314
315         VectorCopy( mins, minimap.mins );
316         VectorSubtract( maxs, mins, minimap.size );
317
318         // line compatible to nexuiz mapinfo
319         Sys_Printf( "size_texcoords %f %f %f %f %f %f\n", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2] );
320 }
321
322 /*
323    MiniMapSetupBrushes()
324    determines solid non-sky brushes in the world
325  */
326
327 void MiniMapSetupBrushes( void ){
328         SetupBrushesFlags( C_SOLID | C_SKY, C_SOLID, 0, 0 );
329         // at least one must be solid
330         // none may be sky
331         // not all may be nodraw
332 }
333
334 qboolean MiniMapEvaluateSampleOffsets( int *bestj, int *bestk, float *bestval ){
335         float val, dx, dy;
336         int j, k;
337
338         *bestj = *bestk = -1;
339         *bestval = 3; /* max possible val is 2 */
340
341         for ( j = 0; j < minimap.samples; ++j )
342                 for ( k = j + 1; k < minimap.samples; ++k )
343                 {
344                         dx = minimap.sample_offsets[2 * j + 0] - minimap.sample_offsets[2 * k + 0];
345                         dy = minimap.sample_offsets[2 * j + 1] - minimap.sample_offsets[2 * k + 1];
346                         if ( dx > +0.5 ) {
347                                 dx -= 1;
348                         }
349                         if ( dx < -0.5 ) {
350                                 dx += 1;
351                         }
352                         if ( dy > +0.5 ) {
353                                 dy -= 1;
354                         }
355                         if ( dy < -0.5 ) {
356                                 dy += 1;
357                         }
358                         val = dx * dx + dy * dy;
359                         if ( val < *bestval ) {
360                                 *bestj = j;
361                                 *bestk = k;
362                                 *bestval = val;
363                         }
364                 }
365
366         return *bestval < 3;
367 }
368
369 void MiniMapMakeSampleOffsets(){
370         int i, j, k, jj, kk;
371         float val, valj, valk, sx, sy, rx, ry;
372
373         Sys_Printf( "Generating good sample offsets (this may take a while)...\n" );
374
375         /* start with entirely random samples */
376         for ( i = 0; i < minimap.samples; ++i )
377         {
378                 minimap.sample_offsets[2 * i + 0] = Random();
379                 minimap.sample_offsets[2 * i + 1] = Random();
380         }
381
382         for ( i = 0; i < 1000; ++i )
383         {
384                 if ( MiniMapEvaluateSampleOffsets( &j, &k, &val ) ) {
385                         sx = minimap.sample_offsets[2 * j + 0];
386                         sy = minimap.sample_offsets[2 * j + 1];
387                         minimap.sample_offsets[2 * j + 0] = rx = Random();
388                         minimap.sample_offsets[2 * j + 1] = ry = Random();
389                         if ( !MiniMapEvaluateSampleOffsets( &jj, &kk, &valj ) ) {
390                                 valj = -1;
391                         }
392                         minimap.sample_offsets[2 * j + 0] = sx;
393                         minimap.sample_offsets[2 * j + 1] = sy;
394
395                         sx = minimap.sample_offsets[2 * k + 0];
396                         sy = minimap.sample_offsets[2 * k + 1];
397                         minimap.sample_offsets[2 * k + 0] = rx;
398                         minimap.sample_offsets[2 * k + 1] = ry;
399                         if ( !MiniMapEvaluateSampleOffsets( &jj, &kk, &valk ) ) {
400                                 valk = -1;
401                         }
402                         minimap.sample_offsets[2 * k + 0] = sx;
403                         minimap.sample_offsets[2 * k + 1] = sy;
404
405                         if ( valj > valk ) {
406                                 if ( valj > val ) {
407                                         /* valj is the greatest */
408                                         minimap.sample_offsets[2 * j + 0] = rx;
409                                         minimap.sample_offsets[2 * j + 1] = ry;
410                                         i = -1;
411                                 }
412                                 else
413                                 {
414                                         /* valj is the greater and it is useless - forget it */
415                                 }
416                         }
417                         else
418                         {
419                                 if ( valk > val ) {
420                                         /* valk is the greatest */
421                                         minimap.sample_offsets[2 * k + 0] = rx;
422                                         minimap.sample_offsets[2 * k + 1] = ry;
423                                         i = -1;
424                                 }
425                                 else
426                                 {
427                                         /* valk is the greater and it is useless - forget it */
428                                 }
429                         }
430                 }
431                 else{
432                         break;
433                 }
434         }
435 }
436
437 void MergeRelativePath( char *out, const char *absolute, const char *relative ){
438         const char *endpos = absolute + strlen( absolute );
439         while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
440                 --endpos;
441         while ( relative[0] == '.' && relative[1] == '.' && ( relative[2] == '/' || relative[2] == '\\' ) )
442         {
443                 relative += 3;
444                 while ( endpos != absolute )
445                 {
446                         --endpos;
447                         if ( *endpos == '/' || *endpos == '\\' ) {
448                                 break;
449                         }
450                 }
451                 while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
452                         --endpos;
453         }
454         memcpy( out, absolute, endpos - absolute );
455         out[endpos - absolute] = '/';
456         strcpy( out + ( endpos - absolute + 1 ), relative );
457 }
458
459 int MiniMapBSPMain( int argc, char **argv ){
460         char minimapFilename[1024];
461         char basename[1024];
462         char path[1024];
463         char relativeMinimapFilename[1024];
464         qboolean autolevel;
465         float minimapSharpen;
466         float border;
467         byte *data4b, *p;
468         float *q;
469         int x, y;
470         int i;
471         miniMapMode_t mode;
472         vec3_t mins, maxs;
473         qboolean keepaspect;
474
475         /* arg checking */
476         if ( argc < 2 ) {
477                 Sys_Printf( "Usage: q3map [-v] -minimap [-size n] [-sharpen f] [-samples n | -random n] [-o filename.tga] [-minmax Xmin Ymin Zmin Xmax Ymax Zmax] <mapname>\n" );
478                 return 0;
479         }
480
481         /* load the BSP first */
482         strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
483         StripExtension( source );
484         DefaultExtension( source, ".bsp" );
485         Sys_Printf( "Loading %s\n", source );
486         BeginMapShaderFile( source );
487         LoadShaderInfo();
488         LoadBSPFile( source );
489
490         minimap.model = &bspModels[0];
491         VectorCopy( minimap.model->mins, mins );
492         VectorCopy( minimap.model->maxs, maxs );
493
494         *minimapFilename = 0;
495         minimapSharpen = game->miniMapSharpen;
496         minimap.width = minimap.height = game->miniMapSize;
497         border = game->miniMapBorder;
498         keepaspect = game->miniMapKeepAspect;
499         mode = game->miniMapMode;
500
501         autolevel = qfalse;
502         minimap.samples = 1;
503         minimap.sample_offsets = NULL;
504         minimap.boost = 1.0;
505         minimap.brightness = 0.0;
506         minimap.contrast = 1.0;
507
508         /* process arguments */
509         for ( i = 1; i < ( argc - 1 ); i++ )
510         {
511                 if ( !strcmp( argv[ i ],  "-size" ) ) {
512                         minimap.width = minimap.height = atoi( argv[i + 1] );
513                         i++;
514                         Sys_Printf( "Image size set to %i\n", minimap.width );
515                 }
516                 else if ( !strcmp( argv[ i ],  "-sharpen" ) ) {
517                         minimapSharpen = atof( argv[i + 1] );
518                         i++;
519                         Sys_Printf( "Sharpening coefficient set to %f\n", minimapSharpen );
520                 }
521                 else if ( !strcmp( argv[ i ],  "-samples" ) ) {
522                         minimap.samples = atoi( argv[i + 1] );
523                         i++;
524                         Sys_Printf( "Samples set to %i\n", minimap.samples );
525                         if ( minimap.sample_offsets ) {
526                                 free( minimap.sample_offsets );
527                         }
528                         minimap.sample_offsets = malloc( 2 * sizeof( *minimap.sample_offsets ) * minimap.samples );
529                         MiniMapMakeSampleOffsets();
530                 }
531                 else if ( !strcmp( argv[ i ],  "-random" ) ) {
532                         minimap.samples = atoi( argv[i + 1] );
533                         i++;
534                         Sys_Printf( "Random samples set to %i\n", minimap.samples );
535                         if ( minimap.sample_offsets ) {
536                                 free( minimap.sample_offsets );
537                         }
538                         minimap.sample_offsets = NULL;
539                 }
540                 else if ( !strcmp( argv[ i ],  "-border" ) ) {
541                         border = atof( argv[i + 1] );
542                         i++;
543                         Sys_Printf( "Border set to %f\n", border );
544                 }
545                 else if ( !strcmp( argv[ i ],  "-keepaspect" ) ) {
546                         keepaspect = qtrue;
547                         Sys_Printf( "Keeping aspect ratio by letterboxing\n", border );
548                 }
549                 else if ( !strcmp( argv[ i ],  "-nokeepaspect" ) ) {
550                         keepaspect = qfalse;
551                         Sys_Printf( "Not keeping aspect ratio\n", border );
552                 }
553                 else if ( !strcmp( argv[ i ],  "-o" ) ) {
554                         strcpy( minimapFilename, argv[i + 1] );
555                         i++;
556                         Sys_Printf( "Output file name set to %s\n", minimapFilename );
557                 }
558                 else if ( !strcmp( argv[ i ],  "-minmax" ) && i < ( argc - 7 ) ) {
559                         mins[0] = atof( argv[i + 1] );
560                         mins[1] = atof( argv[i + 2] );
561                         mins[2] = atof( argv[i + 3] );
562                         maxs[0] = atof( argv[i + 4] );
563                         maxs[1] = atof( argv[i + 5] );
564                         maxs[2] = atof( argv[i + 6] );
565                         i += 6;
566                         Sys_Printf( "Map mins/maxs overridden\n" );
567                 }
568                 else if ( !strcmp( argv[ i ],  "-gray" ) ) {
569                         mode = MINIMAP_MODE_GRAY;
570                         Sys_Printf( "Writing as white-on-black image\n" );
571                 }
572                 else if ( !strcmp( argv[ i ],  "-black" ) ) {
573                         mode = MINIMAP_MODE_BLACK;
574                         Sys_Printf( "Writing as black alpha image\n" );
575                 }
576                 else if ( !strcmp( argv[ i ],  "-white" ) ) {
577                         mode = MINIMAP_MODE_WHITE;
578                         Sys_Printf( "Writing as white alpha image\n" );
579                 }
580                 else if ( !strcmp( argv[ i ],  "-boost" ) && i < ( argc - 2 ) ) {
581                         minimap.boost = atof( argv[i + 1] );
582                         i++;
583                         Sys_Printf( "Contrast boost set to %f\n", minimap.boost );
584                 }
585                 else if ( !strcmp( argv[ i ],  "-brightness" ) && i < ( argc - 2 ) ) {
586                         minimap.brightness = atof( argv[i + 1] );
587                         i++;
588                         Sys_Printf( "Brightness set to %f\n", minimap.brightness );
589                 }
590                 else if ( !strcmp( argv[ i ],  "-contrast" ) && i < ( argc - 2 ) ) {
591                         minimap.contrast = atof( argv[i + 1] );
592                         i++;
593                         Sys_Printf( "Contrast set to %f\n", minimap.contrast );
594                 }
595                 else if ( !strcmp( argv[ i ],  "-autolevel" ) ) {
596                         autolevel = qtrue;
597                         Sys_Printf( "Auto level enabled\n", border );
598                 }
599                 else if ( !strcmp( argv[ i ],  "-noautolevel" ) ) {
600                         autolevel = qfalse;
601                         Sys_Printf( "Auto level disabled\n", border );
602                 }
603         }
604
605         MiniMapMakeMinsMaxs( mins, maxs, border, keepaspect );
606
607         if ( !*minimapFilename ) {
608                 ExtractFileBase( source, basename );
609                 ExtractFilePath( source, path );
610                 sprintf( relativeMinimapFilename, game->miniMapNameFormat, basename );
611                 MergeRelativePath( minimapFilename, path, relativeMinimapFilename );
612                 Sys_Printf( "Output file name automatically set to %s\n", minimapFilename );
613         }
614         ExtractFilePath( minimapFilename, path );
615         Q_mkdir( path );
616
617         if ( minimapSharpen >= 0 ) {
618                 minimap.sharpen_centermult = 8 * minimapSharpen + 1;
619                 minimap.sharpen_boxmult    =    -minimapSharpen;
620         }
621
622         minimap.data1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
623         data4b = safe_malloc( minimap.width * minimap.height * 4 );
624         if ( minimapSharpen >= 0 ) {
625                 minimap.sharpendata1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
626         }
627
628         MiniMapSetupBrushes();
629
630         if ( minimap.samples <= 1 ) {
631                 Sys_Printf( "\n--- MiniMapNoSupersampling (%d) ---\n", minimap.height );
632                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapNoSupersampling );
633         }
634         else
635         {
636                 if ( minimap.sample_offsets ) {
637                         Sys_Printf( "\n--- MiniMapSupersampled (%d) ---\n", minimap.height );
638                         RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSupersampled );
639                 }
640                 else
641                 {
642                         Sys_Printf( "\n--- MiniMapRandomlySupersampled (%d) ---\n", minimap.height );
643                         RunThreadsOnIndividual( minimap.height, qtrue, MiniMapRandomlySupersampled );
644                 }
645         }
646
647         if ( minimap.boost != 1.0 ) {
648                 Sys_Printf( "\n--- MiniMapContrastBoost (%d) ---\n", minimap.height );
649                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapContrastBoost );
650         }
651
652         if ( autolevel ) {
653                 Sys_Printf( "\n--- MiniMapAutoLevel (%d) ---\n", minimap.height );
654                 float mi = 1, ma = 0;
655                 float s, o;
656
657                 // TODO threads!
658                 q = minimap.data1f;
659                 for ( y = 0; y < minimap.height; ++y )
660                         for ( x = 0; x < minimap.width; ++x )
661                         {
662                                 float v = *q++;
663                                 if ( v < mi ) {
664                                         mi = v;
665                                 }
666                                 if ( v > ma ) {
667                                         ma = v;
668                                 }
669                         }
670                 if ( ma > mi ) {
671                         s = 1 / ( ma - mi );
672                         o = mi / ( ma - mi );
673
674                         // equations:
675                         //   brightness + contrast * v
676                         // after autolevel:
677                         //   brightness + contrast * (v * s - o)
678                         // =
679                         //   (brightness - contrast * o) + (contrast * s) * v
680                         minimap.brightness = minimap.brightness - minimap.contrast * o;
681                         minimap.contrast *= s;
682
683                         Sys_Printf( "Auto level: Brightness changed to %f\n", minimap.brightness );
684                         Sys_Printf( "Auto level: Contrast changed to %f\n", minimap.contrast );
685                 }
686                 else{
687                         Sys_Printf( "Auto level: failed because all pixels are the same value\n" );
688                 }
689         }
690
691         if ( minimap.brightness != 0 || minimap.contrast != 1 ) {
692                 Sys_Printf( "\n--- MiniMapBrightnessContrast (%d) ---\n", minimap.height );
693                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapBrightnessContrast );
694         }
695
696         if ( minimap.sharpendata1f ) {
697                 Sys_Printf( "\n--- MiniMapSharpen (%d) ---\n", minimap.height );
698                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSharpen );
699                 q = minimap.sharpendata1f;
700         }
701         else
702         {
703                 q = minimap.data1f;
704         }
705
706         Sys_Printf( "\nConverting..." );
707
708         switch ( mode )
709         {
710         case MINIMAP_MODE_GRAY:
711                 p = data4b;
712                 for ( y = 0; y < minimap.height; ++y )
713                         for ( x = 0; x < minimap.width; ++x )
714                         {
715                                 byte b;
716                                 float v = *q++;
717                                 if ( v < 0 ) {
718                                         v = 0;
719                                 }
720                                 if ( v > 255.0 / 256.0 ) {
721                                         v = 255.0 / 256.0;
722                                 }
723                                 b = v * 256;
724                                 *p++ = b;
725                         }
726                 Sys_Printf( " writing to %s...", minimapFilename );
727                 WriteTGAGray( minimapFilename, data4b, minimap.width, minimap.height );
728                 break;
729         case MINIMAP_MODE_BLACK:
730                 p = data4b;
731                 for ( y = 0; y < minimap.height; ++y )
732                         for ( x = 0; x < minimap.width; ++x )
733                         {
734                                 byte b;
735                                 float v = *q++;
736                                 if ( v < 0 ) {
737                                         v = 0;
738                                 }
739                                 if ( v > 255.0 / 256.0 ) {
740                                         v = 255.0 / 256.0;
741                                 }
742                                 b = v * 256;
743                                 *p++ = 0;
744                                 *p++ = 0;
745                                 *p++ = 0;
746                                 *p++ = b;
747                         }
748                 Sys_Printf( " writing to %s...", minimapFilename );
749                 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
750                 break;
751         case MINIMAP_MODE_WHITE:
752                 p = data4b;
753                 for ( y = 0; y < minimap.height; ++y )
754                         for ( x = 0; x < minimap.width; ++x )
755                         {
756                                 byte b;
757                                 float v = *q++;
758                                 if ( v < 0 ) {
759                                         v = 0;
760                                 }
761                                 if ( v > 255.0 / 256.0 ) {
762                                         v = 255.0 / 256.0;
763                                 }
764                                 b = v * 256;
765                                 *p++ = 255;
766                                 *p++ = 255;
767                                 *p++ = 255;
768                                 *p++ = b;
769                         }
770                 Sys_Printf( " writing to %s...", minimapFilename );
771                 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
772                 break;
773         }
774
775         Sys_Printf( " done.\n" );
776
777         /* return to sender */
778         return 0;
779 }