]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/minimap.c
netradiant: strip 16-bit png to 8-bit, fix #153
[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 // modify maxs and mins in place, copy them before calling this!
286 void MiniMapMakeMinsMaxs( vec3_t mins, vec3_t maxs, float border, qboolean keepaspect ){
287         vec3_t extend;
288
289         // line compatible to nexuiz mapinfo
290         Sys_Printf( "size %f %f %f %f %f %f\n", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2] );
291
292         if ( keepaspect ) {
293                 VectorSubtract( maxs, mins, extend );
294                 if ( extend[1] > extend[0] ) {
295                         mins[0] -= ( extend[1] - extend[0] ) * 0.5;
296                         maxs[0] += ( extend[1] - extend[0] ) * 0.5;
297                 }
298                 else
299                 {
300                         mins[1] -= ( extend[0] - extend[1] ) * 0.5;
301                         maxs[1] += ( extend[0] - extend[1] ) * 0.5;
302                 }
303         }
304
305         /* border: amount of black area around the image */
306         /* input: border, 1-2*border, border but we need border/(1-2*border) */
307
308         VectorSubtract( maxs, mins, extend );
309         VectorScale( extend, border / ( 1 - 2 * border ), extend );
310
311         VectorSubtract( mins, extend, mins );
312         VectorAdd( maxs, extend, maxs );
313
314         VectorCopy( mins, minimap.mins );
315         VectorSubtract( maxs, mins, minimap.size );
316
317         // line compatible to nexuiz mapinfo
318         Sys_Printf( "size_texcoords %f %f %f %f %f %f\n", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2] );
319 }
320
321 /*
322    MiniMapSetupBrushes()
323    determines solid non-sky brushes in the world
324  */
325
326 void MiniMapSetupBrushes( void ){
327         SetupBrushesFlags( C_SOLID | C_SKY, C_SOLID, 0, 0 );
328         // at least one must be solid
329         // none may be sky
330         // not all may be nodraw
331 }
332
333 qboolean MiniMapEvaluateSampleOffsets( int *bestj, int *bestk, float *bestval ){
334         float val, dx, dy;
335         int j, k;
336
337         *bestj = *bestk = -1;
338         *bestval = 3; /* max possible val is 2 */
339
340         for ( j = 0; j < minimap.samples; ++j )
341                 for ( k = j + 1; k < minimap.samples; ++k )
342                 {
343                         dx = minimap.sample_offsets[2 * j + 0] - minimap.sample_offsets[2 * k + 0];
344                         dy = minimap.sample_offsets[2 * j + 1] - minimap.sample_offsets[2 * k + 1];
345                         if ( dx > +0.5 ) {
346                                 dx -= 1;
347                         }
348                         if ( dx < -0.5 ) {
349                                 dx += 1;
350                         }
351                         if ( dy > +0.5 ) {
352                                 dy -= 1;
353                         }
354                         if ( dy < -0.5 ) {
355                                 dy += 1;
356                         }
357                         val = dx * dx + dy * dy;
358                         if ( val < *bestval ) {
359                                 *bestj = j;
360                                 *bestk = k;
361                                 *bestval = val;
362                         }
363                 }
364
365         return *bestval < 3;
366 }
367
368 void MiniMapMakeSampleOffsets(){
369         int i, j, k, jj, kk;
370         float val, valj, valk, sx, sy, rx, ry;
371
372         Sys_Printf( "Generating good sample offsets (this may take a while)...\n" );
373
374         /* start with entirely random samples */
375         for ( i = 0; i < minimap.samples; ++i )
376         {
377                 minimap.sample_offsets[2 * i + 0] = Random();
378                 minimap.sample_offsets[2 * i + 1] = Random();
379         }
380
381         for ( i = 0; i < 1000; ++i )
382         {
383                 if ( MiniMapEvaluateSampleOffsets( &j, &k, &val ) ) {
384                         sx = minimap.sample_offsets[2 * j + 0];
385                         sy = minimap.sample_offsets[2 * j + 1];
386                         minimap.sample_offsets[2 * j + 0] = rx = Random();
387                         minimap.sample_offsets[2 * j + 1] = ry = Random();
388                         if ( !MiniMapEvaluateSampleOffsets( &jj, &kk, &valj ) ) {
389                                 valj = -1;
390                         }
391                         minimap.sample_offsets[2 * j + 0] = sx;
392                         minimap.sample_offsets[2 * j + 1] = sy;
393
394                         sx = minimap.sample_offsets[2 * k + 0];
395                         sy = minimap.sample_offsets[2 * k + 1];
396                         minimap.sample_offsets[2 * k + 0] = rx;
397                         minimap.sample_offsets[2 * k + 1] = ry;
398                         if ( !MiniMapEvaluateSampleOffsets( &jj, &kk, &valk ) ) {
399                                 valk = -1;
400                         }
401                         minimap.sample_offsets[2 * k + 0] = sx;
402                         minimap.sample_offsets[2 * k + 1] = sy;
403
404                         if ( valj > valk ) {
405                                 if ( valj > val ) {
406                                         /* valj is the greatest */
407                                         minimap.sample_offsets[2 * j + 0] = rx;
408                                         minimap.sample_offsets[2 * j + 1] = ry;
409                                         i = -1;
410                                 }
411                                 else
412                                 {
413                                         /* valj is the greater and it is useless - forget it */
414                                 }
415                         }
416                         else
417                         {
418                                 if ( valk > val ) {
419                                         /* valk is the greatest */
420                                         minimap.sample_offsets[2 * k + 0] = rx;
421                                         minimap.sample_offsets[2 * k + 1] = ry;
422                                         i = -1;
423                                 }
424                                 else
425                                 {
426                                         /* valk is the greater and it is useless - forget it */
427                                 }
428                         }
429                 }
430                 else{
431                         break;
432                 }
433         }
434 }
435
436 void MergeRelativePath( char *out, const char *absolute, const char *relative ){
437         const char *endpos = absolute + strlen( absolute );
438         while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
439                 --endpos;
440         while ( relative[0] == '.' && relative[1] == '.' && ( relative[2] == '/' || relative[2] == '\\' ) )
441         {
442                 relative += 3;
443                 while ( endpos != absolute )
444                 {
445                         --endpos;
446                         if ( *endpos == '/' || *endpos == '\\' ) {
447                                 break;
448                         }
449                 }
450                 while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
451                         --endpos;
452         }
453         memcpy( out, absolute, endpos - absolute );
454         out[endpos - absolute] = '/';
455         strcpy( out + ( endpos - absolute + 1 ), relative );
456 }
457
458 int MiniMapBSPMain( int argc, char **argv ){
459         char minimapFilename[1024];
460         char basename[1024];
461         char path[1024];
462         char relativeMinimapFilename[1024];
463         qboolean autolevel;
464         float minimapSharpen;
465         float border;
466         byte *data4b, *p;
467         float *q;
468         int x, y;
469         int i;
470         miniMapMode_t mode;
471         vec3_t mins, maxs;
472         qboolean keepaspect;
473
474         /* arg checking */
475         if ( argc < 2 ) {
476                 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" );
477                 return 0;
478         }
479
480         /* load the BSP first */
481         strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
482         StripExtension( source );
483         DefaultExtension( source, ".bsp" );
484         Sys_Printf( "Loading %s\n", source );
485         LoadShaderInfo();
486         LoadBSPFile( source );
487
488         minimap.model = &bspModels[0];
489         VectorCopy( minimap.model->mins, mins );
490         VectorCopy( minimap.model->maxs, maxs );
491
492         *minimapFilename = 0;
493         minimapSharpen = game->miniMapSharpen;
494         minimap.width = minimap.height = game->miniMapSize;
495         border = game->miniMapBorder;
496         keepaspect = game->miniMapKeepAspect;
497         mode = game->miniMapMode;
498
499         autolevel = qfalse;
500         minimap.samples = 1;
501         minimap.sample_offsets = NULL;
502         minimap.boost = 1.0;
503         minimap.brightness = 0.0;
504         minimap.contrast = 1.0;
505
506         /* process arguments */
507         for ( i = 1; i < ( argc - 1 ); i++ )
508         {
509                 if ( !strcmp( argv[ i ],  "-size" ) ) {
510                         minimap.width = minimap.height = atoi( argv[i + 1] );
511                         i++;
512                         Sys_Printf( "Image size set to %i\n", minimap.width );
513                 }
514                 else if ( !strcmp( argv[ i ],  "-sharpen" ) ) {
515                         minimapSharpen = atof( argv[i + 1] );
516                         i++;
517                         Sys_Printf( "Sharpening coefficient set to %f\n", minimapSharpen );
518                 }
519                 else if ( !strcmp( argv[ i ],  "-samples" ) ) {
520                         minimap.samples = atoi( argv[i + 1] );
521                         i++;
522                         Sys_Printf( "Samples set to %i\n", minimap.samples );
523                         if ( minimap.sample_offsets ) {
524                                 free( minimap.sample_offsets );
525                         }
526                         minimap.sample_offsets = malloc( 2 * sizeof( *minimap.sample_offsets ) * minimap.samples );
527                         MiniMapMakeSampleOffsets();
528                 }
529                 else if ( !strcmp( argv[ i ],  "-random" ) ) {
530                         minimap.samples = atoi( argv[i + 1] );
531                         i++;
532                         Sys_Printf( "Random samples set to %i\n", minimap.samples );
533                         if ( minimap.sample_offsets ) {
534                                 free( minimap.sample_offsets );
535                         }
536                         minimap.sample_offsets = NULL;
537                 }
538                 else if ( !strcmp( argv[ i ],  "-border" ) ) {
539                         border = atof( argv[i + 1] );
540                         i++;
541                         Sys_Printf( "Border set to %f\n", border );
542                 }
543                 else if ( !strcmp( argv[ i ],  "-keepaspect" ) ) {
544                         keepaspect = qtrue;
545                         Sys_Printf( "Keeping aspect ratio by letterboxing\n", border );
546                 }
547                 else if ( !strcmp( argv[ i ],  "-nokeepaspect" ) ) {
548                         keepaspect = qfalse;
549                         Sys_Printf( "Not keeping aspect ratio\n", border );
550                 }
551                 else if ( !strcmp( argv[ i ],  "-o" ) ) {
552                         strcpy( minimapFilename, argv[i + 1] );
553                         i++;
554                         Sys_Printf( "Output file name set to %s\n", minimapFilename );
555                 }
556                 else if ( !strcmp( argv[ i ],  "-minmax" ) && i < ( argc - 7 ) ) {
557                         mins[0] = atof( argv[i + 1] );
558                         mins[1] = atof( argv[i + 2] );
559                         mins[2] = atof( argv[i + 3] );
560                         maxs[0] = atof( argv[i + 4] );
561                         maxs[1] = atof( argv[i + 5] );
562                         maxs[2] = atof( argv[i + 6] );
563                         i += 6;
564                         Sys_Printf( "Map mins/maxs overridden\n" );
565                 }
566                 else if ( !strcmp( argv[ i ],  "-gray" ) ) {
567                         mode = MINIMAP_MODE_GRAY;
568                         Sys_Printf( "Writing as white-on-black image\n" );
569                 }
570                 else if ( !strcmp( argv[ i ],  "-black" ) ) {
571                         mode = MINIMAP_MODE_BLACK;
572                         Sys_Printf( "Writing as black alpha image\n" );
573                 }
574                 else if ( !strcmp( argv[ i ],  "-white" ) ) {
575                         mode = MINIMAP_MODE_WHITE;
576                         Sys_Printf( "Writing as white alpha image\n" );
577                 }
578                 else if ( !strcmp( argv[ i ],  "-boost" ) && i < ( argc - 2 ) ) {
579                         minimap.boost = atof( argv[i + 1] );
580                         i++;
581                         Sys_Printf( "Contrast boost set to %f\n", minimap.boost );
582                 }
583                 else if ( !strcmp( argv[ i ],  "-brightness" ) && i < ( argc - 2 ) ) {
584                         minimap.brightness = atof( argv[i + 1] );
585                         i++;
586                         Sys_Printf( "Brightness set to %f\n", minimap.brightness );
587                 }
588                 else if ( !strcmp( argv[ i ],  "-contrast" ) && i < ( argc - 2 ) ) {
589                         minimap.contrast = atof( argv[i + 1] );
590                         i++;
591                         Sys_Printf( "Contrast set to %f\n", minimap.contrast );
592                 }
593                 else if ( !strcmp( argv[ i ],  "-autolevel" ) ) {
594                         autolevel = qtrue;
595                         Sys_Printf( "Auto level enabled\n", border );
596                 }
597                 else if ( !strcmp( argv[ i ],  "-noautolevel" ) ) {
598                         autolevel = qfalse;
599                         Sys_Printf( "Auto level disabled\n", border );
600                 }
601         }
602
603         vec3_t mins_out, maxs_out;
604         VectorCopy( mins, mins_out );
605         VectorCopy( maxs, maxs_out );
606         MiniMapMakeMinsMaxs( mins_out, maxs_out, border, keepaspect );
607
608         if ( !*minimapFilename ) {
609                 ExtractFileBase( source, basename );
610                 ExtractFilePath( source, path );
611                 sprintf( relativeMinimapFilename, game->miniMapNameFormat, basename );
612                 MergeRelativePath( minimapFilename, path, relativeMinimapFilename );
613                 Sys_Printf( "Output file name automatically set to %s\n", minimapFilename );
614         }
615         ExtractFilePath( minimapFilename, path );
616         Q_mkdir( path );
617
618         if ( minimapSharpen >= 0 ) {
619                 minimap.sharpen_centermult = 8 * minimapSharpen + 1;
620                 minimap.sharpen_boxmult    =    -minimapSharpen;
621         }
622
623         minimap.data1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
624         data4b = safe_malloc( minimap.width * minimap.height * 4 );
625         if ( minimapSharpen >= 0 ) {
626                 minimap.sharpendata1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
627         }
628
629         MiniMapSetupBrushes();
630
631         if ( minimap.samples <= 1 ) {
632                 Sys_Printf( "\n--- MiniMapNoSupersampling (%d) ---\n", minimap.height );
633                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapNoSupersampling );
634         }
635         else
636         {
637                 if ( minimap.sample_offsets ) {
638                         Sys_Printf( "\n--- MiniMapSupersampled (%d) ---\n", minimap.height );
639                         RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSupersampled );
640                 }
641                 else
642                 {
643                         Sys_Printf( "\n--- MiniMapRandomlySupersampled (%d) ---\n", minimap.height );
644                         RunThreadsOnIndividual( minimap.height, qtrue, MiniMapRandomlySupersampled );
645                 }
646         }
647
648         if ( minimap.boost != 1.0 ) {
649                 Sys_Printf( "\n--- MiniMapContrastBoost (%d) ---\n", minimap.height );
650                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapContrastBoost );
651         }
652
653         if ( autolevel ) {
654                 Sys_Printf( "\n--- MiniMapAutoLevel (%d) ---\n", minimap.height );
655                 float mi = 1, ma = 0;
656                 float s, o;
657
658                 // TODO threads!
659                 q = minimap.data1f;
660                 for ( y = 0; y < minimap.height; ++y )
661                         for ( x = 0; x < minimap.width; ++x )
662                         {
663                                 float v = *q++;
664                                 if ( v < mi ) {
665                                         mi = v;
666                                 }
667                                 if ( v > ma ) {
668                                         ma = v;
669                                 }
670                         }
671                 if ( ma > mi ) {
672                         s = 1 / ( ma - mi );
673                         o = mi / ( ma - mi );
674
675                         // equations:
676                         //   brightness + contrast * v
677                         // after autolevel:
678                         //   brightness + contrast * (v * s - o)
679                         // =
680                         //   (brightness - contrast * o) + (contrast * s) * v
681                         minimap.brightness = minimap.brightness - minimap.contrast * o;
682                         minimap.contrast *= s;
683
684                         Sys_Printf( "Auto level: Brightness changed to %f\n", minimap.brightness );
685                         Sys_Printf( "Auto level: Contrast changed to %f\n", minimap.contrast );
686                 }
687                 else{
688                         Sys_Printf( "Auto level: failed because all pixels are the same value\n" );
689                 }
690         }
691
692         if ( minimap.brightness != 0 || minimap.contrast != 1 ) {
693                 Sys_Printf( "\n--- MiniMapBrightnessContrast (%d) ---\n", minimap.height );
694                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapBrightnessContrast );
695         }
696
697         if ( minimap.sharpendata1f ) {
698                 Sys_Printf( "\n--- MiniMapSharpen (%d) ---\n", minimap.height );
699                 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSharpen );
700                 q = minimap.sharpendata1f;
701         }
702         else
703         {
704                 q = minimap.data1f;
705         }
706
707         Sys_Printf( "\nConverting..." );
708
709         switch ( mode )
710         {
711         case MINIMAP_MODE_GRAY:
712                 p = data4b;
713                 for ( y = 0; y < minimap.height; ++y )
714                         for ( x = 0; x < minimap.width; ++x )
715                         {
716                                 byte b;
717                                 float v = *q++;
718                                 if ( v < 0 ) {
719                                         v = 0;
720                                 }
721                                 if ( v > 255.0 / 256.0 ) {
722                                         v = 255.0 / 256.0;
723                                 }
724                                 b = v * 256;
725                                 *p++ = b;
726                         }
727                 Sys_Printf( " writing to %s...", minimapFilename );
728                 WriteTGAGray( minimapFilename, data4b, minimap.width, minimap.height );
729                 break;
730         case MINIMAP_MODE_BLACK:
731                 p = data4b;
732                 for ( y = 0; y < minimap.height; ++y )
733                         for ( x = 0; x < minimap.width; ++x )
734                         {
735                                 byte b;
736                                 float v = *q++;
737                                 if ( v < 0 ) {
738                                         v = 0;
739                                 }
740                                 if ( v > 255.0 / 256.0 ) {
741                                         v = 255.0 / 256.0;
742                                 }
743                                 b = v * 256;
744                                 *p++ = 0;
745                                 *p++ = 0;
746                                 *p++ = 0;
747                                 *p++ = b;
748                         }
749                 Sys_Printf( " writing to %s...", minimapFilename );
750                 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
751                 break;
752         case MINIMAP_MODE_WHITE:
753                 p = data4b;
754                 for ( y = 0; y < minimap.height; ++y )
755                         for ( x = 0; x < minimap.width; ++x )
756                         {
757                                 byte b;
758                                 float v = *q++;
759                                 if ( v < 0 ) {
760                                         v = 0;
761                                 }
762                                 if ( v > 255.0 / 256.0 ) {
763                                         v = 255.0 / 256.0;
764                                 }
765                                 b = v * 256;
766                                 *p++ = 255;
767                                 *p++ = 255;
768                                 *p++ = 255;
769                                 *p++ = b;
770                         }
771                 Sys_Printf( " writing to %s...", minimapFilename );
772                 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
773                 break;
774         }
775
776         Sys_Printf( " done.\n" );
777
778         switch ( game->miniMapSidecarFormat )
779         {
780                 case MINIMAP_SIDECAR_UNVANQUISHED:
781                 {
782                         char minimapPathWithoutExt[ 1024 ];
783                         char minimapSidecarFilename[ 1024 ];
784                         char *minimapSidecarExtension = ".minimap";
785                         char *minimapSidecarFormat = ""
786                                 "{\n"
787                                 "\tbackgroundColor 0.0 0.0 0.0 0.333\n"
788                                 "\tzone {\n"
789                                 "\t\tbounds 0 0 0 0 0 0\n"
790                                 "\t\timage \"minimaps/%s\" %f %f %f %f\n"
791                                 "\t}\n"
792                                 "}\n";
793
794                         strcpy( minimapPathWithoutExt, minimapFilename );
795                         StripExtension( minimapPathWithoutExt );
796                         snprintf( minimapSidecarFilename,
797                                 1024 - strlen(minimapSidecarExtension),
798                                 "%s%s",
799                                 minimapPathWithoutExt,
800                                 minimapSidecarExtension );
801
802                         Sys_Printf( "Writing minimap sidecar to %s...", minimapSidecarFilename );
803
804                         FILE *file = fopen( minimapSidecarFilename, "w" );
805                         if ( file == NULL ) {
806                                 Sys_FPrintf( SYS_WRN, "WARNING: Unable to open minimap sidecarr file %s for writing\n", minimapSidecarFilename );
807                                 break;
808                         }
809
810                         fprintf( file,
811                                 minimapSidecarFormat,
812                                 basename,
813                                 mins_out[0], mins_out[1],
814                                 maxs_out[0], maxs_out[1] );
815
816                         fflush( file );
817                         fclose( file );
818
819                         Sys_Printf( " done.\n" );
820
821                         break;
822                 }
823                 case MINIMAP_SIDECAR_NONE:
824                         break;
825         }
826
827         /* return to sender */
828         return 0;
829 }