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 // modify maxs and mins in place, copy them before calling this!
286 void MiniMapMakeMinsMaxs( vec3_t mins, vec3_t maxs, float border, qboolean keepaspect ){
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] );
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;
300 mins[1] -= ( extend[0] - extend[1] ) * 0.5;
301 maxs[1] += ( extend[0] - extend[1] ) * 0.5;
305 /* border: amount of black area around the image */
306 /* input: border, 1-2*border, border but we need border/(1-2*border) */
308 VectorSubtract( maxs, mins, extend );
309 VectorScale( extend, border / ( 1 - 2 * border ), extend );
311 VectorSubtract( mins, extend, mins );
312 VectorAdd( maxs, extend, maxs );
314 VectorCopy( mins, minimap.mins );
315 VectorSubtract( maxs, mins, minimap.size );
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] );
322 MiniMapSetupBrushes()
323 determines solid non-sky brushes in the world
326 void MiniMapSetupBrushes( void ){
327 SetupBrushesFlags( C_SOLID | C_SKY, C_SOLID, 0, 0 );
328 // at least one must be solid
330 // not all may be nodraw
333 qboolean MiniMapEvaluateSampleOffsets( int *bestj, int *bestk, float *bestval ){
337 *bestj = *bestk = -1;
338 *bestval = 3; /* max possible val is 2 */
340 for ( j = 0; j < minimap.samples; ++j )
341 for ( k = j + 1; k < minimap.samples; ++k )
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];
357 val = dx * dx + dy * dy;
358 if ( val < *bestval ) {
368 void MiniMapMakeSampleOffsets(){
370 float val, valj, valk, sx, sy, rx, ry;
372 Sys_Printf( "Generating good sample offsets (this may take a while)...\n" );
374 /* start with entirely random samples */
375 for ( i = 0; i < minimap.samples; ++i )
377 minimap.sample_offsets[2 * i + 0] = Random();
378 minimap.sample_offsets[2 * i + 1] = Random();
381 for ( i = 0; i < 1000; ++i )
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 ) ) {
391 minimap.sample_offsets[2 * j + 0] = sx;
392 minimap.sample_offsets[2 * j + 1] = sy;
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 ) ) {
401 minimap.sample_offsets[2 * k + 0] = sx;
402 minimap.sample_offsets[2 * k + 1] = sy;
406 /* valj is the greatest */
407 minimap.sample_offsets[2 * j + 0] = rx;
408 minimap.sample_offsets[2 * j + 1] = ry;
413 /* valj is the greater and it is useless - forget it */
419 /* valk is the greatest */
420 minimap.sample_offsets[2 * k + 0] = rx;
421 minimap.sample_offsets[2 * k + 1] = ry;
426 /* valk is the greater and it is useless - forget it */
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] == '\\' ) )
440 while ( relative[0] == '.' && relative[1] == '.' && ( relative[2] == '/' || relative[2] == '\\' ) )
443 while ( endpos != absolute )
446 if ( *endpos == '/' || *endpos == '\\' ) {
450 while ( endpos != absolute && ( endpos[-1] == '/' || endpos[-1] == '\\' ) )
453 memcpy( out, absolute, endpos - absolute );
454 out[endpos - absolute] = '/';
455 strcpy( out + ( endpos - absolute + 1 ), relative );
458 int MiniMapBSPMain( int argc, char **argv ){
459 char minimapFilename[1024];
462 char relativeMinimapFilename[1024];
464 float minimapSharpen;
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" );
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 );
486 LoadBSPFile( source );
488 minimap.model = &bspModels[0];
489 VectorCopy( minimap.model->mins, mins );
490 VectorCopy( minimap.model->maxs, maxs );
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;
501 minimap.sample_offsets = NULL;
503 minimap.brightness = 0.0;
504 minimap.contrast = 1.0;
506 /* process arguments */
507 for ( i = 1; i < ( argc - 1 ); i++ )
509 if ( !strcmp( argv[ i ], "-size" ) ) {
510 minimap.width = minimap.height = atoi( argv[i + 1] );
512 Sys_Printf( "Image size set to %i\n", minimap.width );
514 else if ( !strcmp( argv[ i ], "-sharpen" ) ) {
515 minimapSharpen = atof( argv[i + 1] );
517 Sys_Printf( "Sharpening coefficient set to %f\n", minimapSharpen );
519 else if ( !strcmp( argv[ i ], "-samples" ) ) {
520 minimap.samples = atoi( argv[i + 1] );
522 Sys_Printf( "Samples set to %i\n", minimap.samples );
523 if ( minimap.sample_offsets ) {
524 free( minimap.sample_offsets );
526 minimap.sample_offsets = malloc( 2 * sizeof( *minimap.sample_offsets ) * minimap.samples );
527 MiniMapMakeSampleOffsets();
529 else if ( !strcmp( argv[ i ], "-random" ) ) {
530 minimap.samples = atoi( argv[i + 1] );
532 Sys_Printf( "Random samples set to %i\n", minimap.samples );
533 if ( minimap.sample_offsets ) {
534 free( minimap.sample_offsets );
536 minimap.sample_offsets = NULL;
538 else if ( !strcmp( argv[ i ], "-border" ) ) {
539 border = atof( argv[i + 1] );
541 Sys_Printf( "Border set to %f\n", border );
543 else if ( !strcmp( argv[ i ], "-keepaspect" ) ) {
545 Sys_Printf( "Keeping aspect ratio by letterboxing\n", border );
547 else if ( !strcmp( argv[ i ], "-nokeepaspect" ) ) {
549 Sys_Printf( "Not keeping aspect ratio\n", border );
551 else if ( !strcmp( argv[ i ], "-o" ) ) {
552 strcpy( minimapFilename, argv[i + 1] );
554 Sys_Printf( "Output file name set to %s\n", minimapFilename );
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] );
564 Sys_Printf( "Map mins/maxs overridden\n" );
566 else if ( !strcmp( argv[ i ], "-gray" ) ) {
567 mode = MINIMAP_MODE_GRAY;
568 Sys_Printf( "Writing as white-on-black image\n" );
570 else if ( !strcmp( argv[ i ], "-black" ) ) {
571 mode = MINIMAP_MODE_BLACK;
572 Sys_Printf( "Writing as black alpha image\n" );
574 else if ( !strcmp( argv[ i ], "-white" ) ) {
575 mode = MINIMAP_MODE_WHITE;
576 Sys_Printf( "Writing as white alpha image\n" );
578 else if ( !strcmp( argv[ i ], "-boost" ) && i < ( argc - 2 ) ) {
579 minimap.boost = atof( argv[i + 1] );
581 Sys_Printf( "Contrast boost set to %f\n", minimap.boost );
583 else if ( !strcmp( argv[ i ], "-brightness" ) && i < ( argc - 2 ) ) {
584 minimap.brightness = atof( argv[i + 1] );
586 Sys_Printf( "Brightness set to %f\n", minimap.brightness );
588 else if ( !strcmp( argv[ i ], "-contrast" ) && i < ( argc - 2 ) ) {
589 minimap.contrast = atof( argv[i + 1] );
591 Sys_Printf( "Contrast set to %f\n", minimap.contrast );
593 else if ( !strcmp( argv[ i ], "-autolevel" ) ) {
595 Sys_Printf( "Auto level enabled\n", border );
597 else if ( !strcmp( argv[ i ], "-noautolevel" ) ) {
599 Sys_Printf( "Auto level disabled\n", border );
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 );
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 );
615 ExtractFilePath( minimapFilename, path );
618 if ( minimapSharpen >= 0 ) {
619 minimap.sharpen_centermult = 8 * minimapSharpen + 1;
620 minimap.sharpen_boxmult = -minimapSharpen;
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 ) );
629 MiniMapSetupBrushes();
631 if ( minimap.samples <= 1 ) {
632 Sys_Printf( "\n--- MiniMapNoSupersampling (%d) ---\n", minimap.height );
633 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapNoSupersampling );
637 if ( minimap.sample_offsets ) {
638 Sys_Printf( "\n--- MiniMapSupersampled (%d) ---\n", minimap.height );
639 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSupersampled );
643 Sys_Printf( "\n--- MiniMapRandomlySupersampled (%d) ---\n", minimap.height );
644 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapRandomlySupersampled );
648 if ( minimap.boost != 1.0 ) {
649 Sys_Printf( "\n--- MiniMapContrastBoost (%d) ---\n", minimap.height );
650 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapContrastBoost );
654 Sys_Printf( "\n--- MiniMapAutoLevel (%d) ---\n", minimap.height );
655 float mi = 1, ma = 0;
660 for ( y = 0; y < minimap.height; ++y )
661 for ( x = 0; x < minimap.width; ++x )
673 o = mi / ( ma - mi );
676 // brightness + contrast * v
678 // brightness + contrast * (v * s - o)
680 // (brightness - contrast * o) + (contrast * s) * v
681 minimap.brightness = minimap.brightness - minimap.contrast * o;
682 minimap.contrast *= s;
684 Sys_Printf( "Auto level: Brightness changed to %f\n", minimap.brightness );
685 Sys_Printf( "Auto level: Contrast changed to %f\n", minimap.contrast );
688 Sys_Printf( "Auto level: failed because all pixels are the same value\n" );
692 if ( minimap.brightness != 0 || minimap.contrast != 1 ) {
693 Sys_Printf( "\n--- MiniMapBrightnessContrast (%d) ---\n", minimap.height );
694 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapBrightnessContrast );
697 if ( minimap.sharpendata1f ) {
698 Sys_Printf( "\n--- MiniMapSharpen (%d) ---\n", minimap.height );
699 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSharpen );
700 q = minimap.sharpendata1f;
707 Sys_Printf( "\nConverting..." );
711 case MINIMAP_MODE_GRAY:
713 for ( y = 0; y < minimap.height; ++y )
714 for ( x = 0; x < minimap.width; ++x )
721 if ( v > 255.0 / 256.0 ) {
727 Sys_Printf( " writing to %s...", minimapFilename );
728 WriteTGAGray( minimapFilename, data4b, minimap.width, minimap.height );
730 case MINIMAP_MODE_BLACK:
732 for ( y = 0; y < minimap.height; ++y )
733 for ( x = 0; x < minimap.width; ++x )
740 if ( v > 255.0 / 256.0 ) {
749 Sys_Printf( " writing to %s...", minimapFilename );
750 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
752 case MINIMAP_MODE_WHITE:
754 for ( y = 0; y < minimap.height; ++y )
755 for ( x = 0; x < minimap.width; ++x )
762 if ( v > 255.0 / 256.0 ) {
771 Sys_Printf( " writing to %s...", minimapFilename );
772 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
776 Sys_Printf( " done.\n" );
778 switch ( game->miniMapSidecarFormat )
780 case MINIMAP_SIDECAR_UNVANQUISHED:
782 char minimapPathWithoutExt[ 1024 ];
783 char minimapSidecarFilename[ 1024 ];
784 char *minimapSidecarExtension = ".minimap";
785 char *minimapSidecarFormat = ""
787 "\tbackgroundColor 0.0 0.0 0.0 0.333\n"
789 "\t\tbounds 0 0 0 0 0 0\n"
790 "\t\timage \"minimaps/%s\" %f %f %f %f\n"
794 strcpy( minimapPathWithoutExt, minimapFilename );
795 StripExtension( minimapPathWithoutExt );
796 snprintf( minimapSidecarFilename,
797 1024 - strlen(minimapSidecarExtension),
799 minimapPathWithoutExt,
800 minimapSidecarExtension );
802 Sys_Printf( "Writing minimap sidecar to %s...", minimapSidecarFilename );
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 );
811 minimapSidecarFormat,
813 mins_out[0], mins_out[1],
814 maxs_out[0], maxs_out[1] );
819 Sys_Printf( " done.\n" );
823 case MINIMAP_SIDECAR_NONE:
827 /* return to sender */