1 /* -------------------------------------------------------------------------------
3 Copyright (C) 1999-2007 id Software, Inc. and contributors.
4 For a list of contributors, see the accompanying CONTRIBUTORS file.
6 This file is part of GtkRadiant.
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.
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.
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
22 -------------------------------------------------------------------------------
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."
27 ------------------------------------------------------------------------------- */
36 typedef struct minimap_s
42 float *sample_offsets;
43 float sharpen_boxmult;
44 float sharpen_centermult;
45 float boost, brightness, contrast;
52 static minimap_t minimap;
54 qboolean BrushIntersectionWithLine( bspBrush_t *brush, vec3_t start, vec3_t dir, float *t_in, float *t_out ){
56 qboolean in = qfalse, out = qfalse;
57 bspBrushSide_t *sides = &bspBrushSides[brush->firstSide];
59 for ( i = 0; i < brush->numSides; ++i )
61 bspPlane_t *p = &bspPlanes[sides[i].planeNum];
62 float sn = DotProduct( start, p->normal );
63 float dn = DotProduct( dir, p->normal );
66 return qfalse; // outside!
71 float t = ( p->dist - sn ) / dn;
73 if ( !in || t > *t_in ) {
76 // as t_in can only increase, and t_out can only decrease, early out
77 if ( out && *t_in >= *t_out ) {
84 if ( !out || t < *t_out ) {
87 // as t_in can only increase, and t_out can only decrease, early out
88 if ( in && *t_in >= *t_out ) {
98 static float MiniMapSample( float x, float y ){
116 for ( i = 0; i < minimap.model->numBSPBrushes; ++i )
118 bi = minimap.model->firstBSPBrush + i;
119 if ( opaqueBrushes[bi >> 3] & ( 1 << ( bi & 7 ) ) ) {
122 // sort out mins/maxs of the brush
123 s = &bspBrushSides[b->firstSide];
124 if ( x < -bspPlanes[s[0].planeNum].dist ) {
127 if ( x > +bspPlanes[s[1].planeNum].dist ) {
130 if ( y < -bspPlanes[s[2].planeNum].dist ) {
133 if ( y > +bspPlanes[s[3].planeNum].dist ) {
137 if ( BrushIntersectionWithLine( b, org, dir, &t0, &t1 ) ) {
147 void RandomVector2f( float v[2] ){
150 v[0] = 2 * Random() - 1;
151 v[1] = 2 * Random() - 1;
153 while ( v[0] * v[0] + v[1] * v[1] > 1 );
156 static void MiniMapRandomlySupersampled( int y ){
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;
165 for ( x = 0; x < minimap.width; ++x )
167 float xmin = minimap.mins[0] + minimap.size[0] * ( x / (float) minimap.width );
170 for ( i = 0; i < minimap.samples; ++i )
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 */
179 val /= minimap.samples * minimap.size[2];
184 static void MiniMapSupersampled( int y ){
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;
191 for ( x = 0; x < minimap.width; ++x )
193 float xmin = minimap.mins[0] + minimap.size[0] * ( x / (float) minimap.width );
196 for ( i = 0; i < minimap.samples; ++i )
198 float thisval = MiniMapSample(
199 xmin + minimap.sample_offsets[2 * i + 0] * dx,
200 ymin + minimap.sample_offsets[2 * i + 1] * dy
204 val /= minimap.samples * minimap.size[2];
209 static void MiniMapNoSupersampling( int y ){
211 float *p = &minimap.data1f[y * minimap.width];
212 float ymin = minimap.mins[1] + minimap.size[1] * ( ( y + 0.5 ) / (float) minimap.height );
214 for ( x = 0; x < minimap.width; ++x )
216 float xmin = minimap.mins[0] + minimap.size[0] * ( ( x + 0.5 ) / (float) minimap.width );
217 *p++ = MiniMapSample( xmin, ymin ) / minimap.size[2];
221 static void MiniMapSharpen( int y ){
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];
228 for ( x = 0; x < minimap.width; ++x )
230 qboolean left = ( x > 0 );
231 qboolean right = ( x < minimap.width - 1 );
232 float val = p[0] * minimap.sharpen_centermult;
235 val += p[-1 - minimap.width] * minimap.sharpen_boxmult;
237 if ( left && down ) {
238 val += p[-1 + minimap.width] * minimap.sharpen_boxmult;
241 val += p[+1 - minimap.width] * minimap.sharpen_boxmult;
243 if ( right && down ) {
244 val += p[+1 + minimap.width] * minimap.sharpen_boxmult;
248 val += p[-1] * minimap.sharpen_boxmult;
251 val += p[+1] * minimap.sharpen_boxmult;
254 val += p[-minimap.width] * minimap.sharpen_boxmult;
257 val += p[+minimap.width] * minimap.sharpen_boxmult;
265 static void MiniMapContrastBoost( int y ){
267 float *q = &minimap.data1f[y * minimap.width];
268 for ( x = 0; x < minimap.width; ++x )
270 *q = *q * minimap.boost / ( ( minimap.boost - 1 ) * *q + 1 );
275 static void MiniMapBrightnessContrast( int y ){
277 float *q = &minimap.data1f[y * minimap.width];
278 for ( x = 0; x < minimap.width; ++x )
280 *q = *q * minimap.contrast + minimap.brightness;
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 );
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] );
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;
301 mins[1] -= ( extend[0] - extend[1] ) * 0.5;
302 maxs[1] += ( extend[0] - extend[1] ) * 0.5;
306 /* border: amount of black area around the image */
307 /* input: border, 1-2*border, border but we need border/(1-2*border) */
309 VectorSubtract( maxs, mins, extend );
310 VectorScale( extend, border / ( 1 - 2 * border ), extend );
312 VectorSubtract( mins, extend, mins );
313 VectorAdd( maxs, extend, maxs );
315 VectorCopy( mins, minimap.mins );
316 VectorSubtract( maxs, mins, minimap.size );
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] );
323 MiniMapSetupBrushes()
324 determines solid non-sky brushes in the world
327 void MiniMapSetupBrushes( void ){
328 SetupBrushesFlags( C_SOLID | C_SKY, C_SOLID, 0, 0 );
329 // at least one must be solid
331 // not all may be nodraw
334 qboolean MiniMapEvaluateSampleOffsets( int *bestj, int *bestk, float *bestval ){
338 *bestj = *bestk = -1;
339 *bestval = 3; /* max possible val is 2 */
341 for ( j = 0; j < minimap.samples; ++j )
342 for ( k = j + 1; k < minimap.samples; ++k )
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];
358 val = dx * dx + dy * dy;
359 if ( val < *bestval ) {
369 void MiniMapMakeSampleOffsets(){
371 float val, valj, valk, sx, sy, rx, ry;
373 Sys_Printf( "Generating good sample offsets (this may take a while)...\n" );
375 /* start with entirely random samples */
376 for ( i = 0; i < minimap.samples; ++i )
378 minimap.sample_offsets[2 * i + 0] = Random();
379 minimap.sample_offsets[2 * i + 1] = Random();
382 for ( i = 0; i < 1000; ++i )
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 ) ) {
392 minimap.sample_offsets[2 * j + 0] = sx;
393 minimap.sample_offsets[2 * j + 1] = sy;
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 ) ) {
402 minimap.sample_offsets[2 * k + 0] = sx;
403 minimap.sample_offsets[2 * k + 1] = sy;
407 /* valj is the greatest */
408 minimap.sample_offsets[2 * j + 0] = rx;
409 minimap.sample_offsets[2 * j + 1] = ry;
414 /* valj is the greater and it is useless - forget it */
420 /* valk is the greatest */
421 minimap.sample_offsets[2 * k + 0] = rx;
422 minimap.sample_offsets[2 * k + 1] = ry;
427 /* valk is the greater and it is useless - forget it */
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] == '\\' ) )
441 while ( relative[0] == '.' && relative[1] == '.' && ( relative[2] == '/' || relative[2] == '\\' ) )
444 while ( endpos != absolute )
447 if ( *endpos == '/' || *endpos == '\\' ) {
451 while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
454 memcpy( out, absolute, endpos - absolute );
455 out[endpos - absolute] = '/';
456 strcpy( out + ( endpos - absolute + 1 ), relative );
459 int MiniMapBSPMain( int argc, char **argv ){
460 char minimapFilename[1024];
463 char relativeMinimapFilename[1024];
465 float minimapSharpen;
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" );
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 );
487 LoadBSPFile( source );
489 minimap.model = &bspModels[0];
490 VectorCopy( minimap.model->mins, mins );
491 VectorCopy( minimap.model->maxs, maxs );
493 *minimapFilename = 0;
494 minimapSharpen = game->miniMapSharpen;
495 minimap.width = minimap.height = game->miniMapSize;
496 border = game->miniMapBorder;
497 keepaspect = game->miniMapKeepAspect;
498 mode = game->miniMapMode;
502 minimap.sample_offsets = NULL;
504 minimap.brightness = 0.0;
505 minimap.contrast = 1.0;
507 /* process arguments */
508 for ( i = 1; i < ( argc - 1 ); i++ )
510 if ( !strcmp( argv[ i ], "-size" ) ) {
511 minimap.width = minimap.height = atoi( argv[i + 1] );
513 Sys_Printf( "Image size set to %i\n", minimap.width );
515 else if ( !strcmp( argv[ i ], "-sharpen" ) ) {
516 minimapSharpen = atof( argv[i + 1] );
518 Sys_Printf( "Sharpening coefficient set to %f\n", minimapSharpen );
520 else if ( !strcmp( argv[ i ], "-samples" ) ) {
521 minimap.samples = atoi( argv[i + 1] );
523 Sys_Printf( "Samples set to %i\n", minimap.samples );
524 if ( minimap.sample_offsets ) {
525 free( minimap.sample_offsets );
527 minimap.sample_offsets = malloc( 2 * sizeof( *minimap.sample_offsets ) * minimap.samples );
528 MiniMapMakeSampleOffsets();
530 else if ( !strcmp( argv[ i ], "-random" ) ) {
531 minimap.samples = atoi( argv[i + 1] );
533 Sys_Printf( "Random samples set to %i\n", minimap.samples );
534 if ( minimap.sample_offsets ) {
535 free( minimap.sample_offsets );
537 minimap.sample_offsets = NULL;
539 else if ( !strcmp( argv[ i ], "-border" ) ) {
540 border = atof( argv[i + 1] );
542 Sys_Printf( "Border set to %f\n", border );
544 else if ( !strcmp( argv[ i ], "-keepaspect" ) ) {
546 Sys_Printf( "Keeping aspect ratio by letterboxing\n", border );
548 else if ( !strcmp( argv[ i ], "-nokeepaspect" ) ) {
550 Sys_Printf( "Not keeping aspect ratio\n", border );
552 else if ( !strcmp( argv[ i ], "-o" ) ) {
553 strcpy( minimapFilename, argv[i + 1] );
555 Sys_Printf( "Output file name set to %s\n", minimapFilename );
557 else if ( !strcmp( argv[ i ], "-minmax" ) && i < ( argc - 7 ) ) {
558 mins[0] = atof( argv[i + 1] );
559 mins[1] = atof( argv[i + 2] );
560 mins[2] = atof( argv[i + 3] );
561 maxs[0] = atof( argv[i + 4] );
562 maxs[1] = atof( argv[i + 5] );
563 maxs[2] = atof( argv[i + 6] );
565 Sys_Printf( "Map mins/maxs overridden\n" );
567 else if ( !strcmp( argv[ i ], "-gray" ) ) {
568 mode = MINIMAP_MODE_GRAY;
569 Sys_Printf( "Writing as white-on-black image\n" );
571 else if ( !strcmp( argv[ i ], "-black" ) ) {
572 mode = MINIMAP_MODE_BLACK;
573 Sys_Printf( "Writing as black alpha image\n" );
575 else if ( !strcmp( argv[ i ], "-white" ) ) {
576 mode = MINIMAP_MODE_WHITE;
577 Sys_Printf( "Writing as white alpha image\n" );
579 else if ( !strcmp( argv[ i ], "-boost" ) && i < ( argc - 2 ) ) {
580 minimap.boost = atof( argv[i + 1] );
582 Sys_Printf( "Contrast boost set to %f\n", minimap.boost );
584 else if ( !strcmp( argv[ i ], "-brightness" ) && i < ( argc - 2 ) ) {
585 minimap.brightness = atof( argv[i + 1] );
587 Sys_Printf( "Brightness set to %f\n", minimap.brightness );
589 else if ( !strcmp( argv[ i ], "-contrast" ) && i < ( argc - 2 ) ) {
590 minimap.contrast = atof( argv[i + 1] );
592 Sys_Printf( "Contrast set to %f\n", minimap.contrast );
594 else if ( !strcmp( argv[ i ], "-autolevel" ) ) {
596 Sys_Printf( "Auto level enabled\n", border );
598 else if ( !strcmp( argv[ i ], "-noautolevel" ) ) {
600 Sys_Printf( "Auto level disabled\n", border );
604 MiniMapMakeMinsMaxs( mins, maxs, border, keepaspect );
606 if ( !*minimapFilename ) {
607 ExtractFileBase( source, basename );
608 ExtractFilePath( source, path );
609 sprintf( relativeMinimapFilename, game->miniMapNameFormat, basename );
610 MergeRelativePath( minimapFilename, path, relativeMinimapFilename );
611 Sys_Printf( "Output file name automatically set to %s\n", minimapFilename );
613 ExtractFilePath( minimapFilename, path );
616 if ( minimapSharpen >= 0 ) {
617 minimap.sharpen_centermult = 8 * minimapSharpen + 1;
618 minimap.sharpen_boxmult = -minimapSharpen;
621 minimap.data1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
622 data4b = safe_malloc( minimap.width * minimap.height * 4 );
623 if ( minimapSharpen >= 0 ) {
624 minimap.sharpendata1f = safe_malloc( minimap.width * minimap.height * sizeof( *minimap.data1f ) );
627 MiniMapSetupBrushes();
629 if ( minimap.samples <= 1 ) {
630 Sys_Printf( "\n--- MiniMapNoSupersampling (%d) ---\n", minimap.height );
631 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapNoSupersampling );
635 if ( minimap.sample_offsets ) {
636 Sys_Printf( "\n--- MiniMapSupersampled (%d) ---\n", minimap.height );
637 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSupersampled );
641 Sys_Printf( "\n--- MiniMapRandomlySupersampled (%d) ---\n", minimap.height );
642 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapRandomlySupersampled );
646 if ( minimap.boost != 1.0 ) {
647 Sys_Printf( "\n--- MiniMapContrastBoost (%d) ---\n", minimap.height );
648 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapContrastBoost );
652 Sys_Printf( "\n--- MiniMapAutoLevel (%d) ---\n", minimap.height );
653 float mi = 1, ma = 0;
658 for ( y = 0; y < minimap.height; ++y )
659 for ( x = 0; x < minimap.width; ++x )
671 o = mi / ( ma - mi );
674 // brightness + contrast * v
676 // brightness + contrast * (v * s - o)
678 // (brightness - contrast * o) + (contrast * s) * v
679 minimap.brightness = minimap.brightness - minimap.contrast * o;
680 minimap.contrast *= s;
682 Sys_Printf( "Auto level: Brightness changed to %f\n", minimap.brightness );
683 Sys_Printf( "Auto level: Contrast changed to %f\n", minimap.contrast );
686 Sys_Printf( "Auto level: failed because all pixels are the same value\n" );
690 if ( minimap.brightness != 0 || minimap.contrast != 1 ) {
691 Sys_Printf( "\n--- MiniMapBrightnessContrast (%d) ---\n", minimap.height );
692 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapBrightnessContrast );
695 if ( minimap.sharpendata1f ) {
696 Sys_Printf( "\n--- MiniMapSharpen (%d) ---\n", minimap.height );
697 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSharpen );
698 q = minimap.sharpendata1f;
705 Sys_Printf( "\nConverting..." );
709 case MINIMAP_MODE_GRAY:
711 for ( y = 0; y < minimap.height; ++y )
712 for ( x = 0; x < minimap.width; ++x )
719 if ( v > 255.0 / 256.0 ) {
725 Sys_Printf( " writing to %s...", minimapFilename );
726 WriteTGAGray( minimapFilename, data4b, minimap.width, minimap.height );
728 case MINIMAP_MODE_BLACK:
730 for ( y = 0; y < minimap.height; ++y )
731 for ( x = 0; x < minimap.width; ++x )
738 if ( v > 255.0 / 256.0 ) {
747 Sys_Printf( " writing to %s...", minimapFilename );
748 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
750 case MINIMAP_MODE_WHITE:
752 for ( y = 0; y < minimap.height; ++y )
753 for ( x = 0; x < minimap.width; ++x )
760 if ( v > 255.0 / 256.0 ) {
769 Sys_Printf( " writing to %s...", minimapFilename );
770 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
774 Sys_Printf( " done.\n" );
776 /* return to sender */