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 );
486 BeginMapShaderFile( source );
488 LoadBSPFile( source );
490 minimap.model = &bspModels[0];
491 VectorCopy( minimap.model->mins, mins );
492 VectorCopy( minimap.model->maxs, maxs );
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;
503 minimap.sample_offsets = NULL;
505 minimap.brightness = 0.0;
506 minimap.contrast = 1.0;
508 /* process arguments */
509 for ( i = 1; i < ( argc - 1 ); i++ )
511 if ( !strcmp( argv[ i ], "-size" ) ) {
512 minimap.width = minimap.height = atoi( argv[i + 1] );
514 Sys_Printf( "Image size set to %i\n", minimap.width );
516 else if ( !strcmp( argv[ i ], "-sharpen" ) ) {
517 minimapSharpen = atof( argv[i + 1] );
519 Sys_Printf( "Sharpening coefficient set to %f\n", minimapSharpen );
521 else if ( !strcmp( argv[ i ], "-samples" ) ) {
522 minimap.samples = atoi( argv[i + 1] );
524 Sys_Printf( "Samples set to %i\n", minimap.samples );
525 if ( minimap.sample_offsets ) {
526 free( minimap.sample_offsets );
528 minimap.sample_offsets = malloc( 2 * sizeof( *minimap.sample_offsets ) * minimap.samples );
529 MiniMapMakeSampleOffsets();
531 else if ( !strcmp( argv[ i ], "-random" ) ) {
532 minimap.samples = atoi( argv[i + 1] );
534 Sys_Printf( "Random samples set to %i\n", minimap.samples );
535 if ( minimap.sample_offsets ) {
536 free( minimap.sample_offsets );
538 minimap.sample_offsets = NULL;
540 else if ( !strcmp( argv[ i ], "-border" ) ) {
541 border = atof( argv[i + 1] );
543 Sys_Printf( "Border set to %f\n", border );
545 else if ( !strcmp( argv[ i ], "-keepaspect" ) ) {
547 Sys_Printf( "Keeping aspect ratio by letterboxing\n", border );
549 else if ( !strcmp( argv[ i ], "-nokeepaspect" ) ) {
551 Sys_Printf( "Not keeping aspect ratio\n", border );
553 else if ( !strcmp( argv[ i ], "-o" ) ) {
554 strcpy( minimapFilename, argv[i + 1] );
556 Sys_Printf( "Output file name set to %s\n", minimapFilename );
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] );
566 Sys_Printf( "Map mins/maxs overridden\n" );
568 else if ( !strcmp( argv[ i ], "-gray" ) ) {
569 mode = MINIMAP_MODE_GRAY;
570 Sys_Printf( "Writing as white-on-black image\n" );
572 else if ( !strcmp( argv[ i ], "-black" ) ) {
573 mode = MINIMAP_MODE_BLACK;
574 Sys_Printf( "Writing as black alpha image\n" );
576 else if ( !strcmp( argv[ i ], "-white" ) ) {
577 mode = MINIMAP_MODE_WHITE;
578 Sys_Printf( "Writing as white alpha image\n" );
580 else if ( !strcmp( argv[ i ], "-boost" ) && i < ( argc - 2 ) ) {
581 minimap.boost = atof( argv[i + 1] );
583 Sys_Printf( "Contrast boost set to %f\n", minimap.boost );
585 else if ( !strcmp( argv[ i ], "-brightness" ) && i < ( argc - 2 ) ) {
586 minimap.brightness = atof( argv[i + 1] );
588 Sys_Printf( "Brightness set to %f\n", minimap.brightness );
590 else if ( !strcmp( argv[ i ], "-contrast" ) && i < ( argc - 2 ) ) {
591 minimap.contrast = atof( argv[i + 1] );
593 Sys_Printf( "Contrast set to %f\n", minimap.contrast );
595 else if ( !strcmp( argv[ i ], "-autolevel" ) ) {
597 Sys_Printf( "Auto level enabled\n", border );
599 else if ( !strcmp( argv[ i ], "-noautolevel" ) ) {
601 Sys_Printf( "Auto level disabled\n", border );
605 MiniMapMakeMinsMaxs( mins, maxs, border, keepaspect );
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 );
614 ExtractFilePath( minimapFilename, path );
617 if ( minimapSharpen >= 0 ) {
618 minimap.sharpen_centermult = 8 * minimapSharpen + 1;
619 minimap.sharpen_boxmult = -minimapSharpen;
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 ) );
628 MiniMapSetupBrushes();
630 if ( minimap.samples <= 1 ) {
631 Sys_Printf( "\n--- MiniMapNoSupersampling (%d) ---\n", minimap.height );
632 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapNoSupersampling );
636 if ( minimap.sample_offsets ) {
637 Sys_Printf( "\n--- MiniMapSupersampled (%d) ---\n", minimap.height );
638 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSupersampled );
642 Sys_Printf( "\n--- MiniMapRandomlySupersampled (%d) ---\n", minimap.height );
643 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapRandomlySupersampled );
647 if ( minimap.boost != 1.0 ) {
648 Sys_Printf( "\n--- MiniMapContrastBoost (%d) ---\n", minimap.height );
649 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapContrastBoost );
653 Sys_Printf( "\n--- MiniMapAutoLevel (%d) ---\n", minimap.height );
654 float mi = 1, ma = 0;
659 for ( y = 0; y < minimap.height; ++y )
660 for ( x = 0; x < minimap.width; ++x )
672 o = mi / ( ma - mi );
675 // brightness + contrast * v
677 // brightness + contrast * (v * s - o)
679 // (brightness - contrast * o) + (contrast * s) * v
680 minimap.brightness = minimap.brightness - minimap.contrast * o;
681 minimap.contrast *= s;
683 Sys_Printf( "Auto level: Brightness changed to %f\n", minimap.brightness );
684 Sys_Printf( "Auto level: Contrast changed to %f\n", minimap.contrast );
687 Sys_Printf( "Auto level: failed because all pixels are the same value\n" );
691 if ( minimap.brightness != 0 || minimap.contrast != 1 ) {
692 Sys_Printf( "\n--- MiniMapBrightnessContrast (%d) ---\n", minimap.height );
693 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapBrightnessContrast );
696 if ( minimap.sharpendata1f ) {
697 Sys_Printf( "\n--- MiniMapSharpen (%d) ---\n", minimap.height );
698 RunThreadsOnIndividual( minimap.height, qtrue, MiniMapSharpen );
699 q = minimap.sharpendata1f;
706 Sys_Printf( "\nConverting..." );
710 case MINIMAP_MODE_GRAY:
712 for ( y = 0; y < minimap.height; ++y )
713 for ( x = 0; x < minimap.width; ++x )
720 if ( v > 255.0 / 256.0 ) {
726 Sys_Printf( " writing to %s...", minimapFilename );
727 WriteTGAGray( minimapFilename, data4b, minimap.width, minimap.height );
729 case MINIMAP_MODE_BLACK:
731 for ( y = 0; y < minimap.height; ++y )
732 for ( x = 0; x < minimap.width; ++x )
739 if ( v > 255.0 / 256.0 ) {
748 Sys_Printf( " writing to %s...", minimapFilename );
749 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
751 case MINIMAP_MODE_WHITE:
753 for ( y = 0; y < minimap.height; ++y )
754 for ( x = 0; x < minimap.width; ++x )
761 if ( v > 255.0 / 256.0 ) {
770 Sys_Printf( " writing to %s...", minimapFilename );
771 WriteTGA( minimapFilename, data4b, minimap.width, minimap.height );
775 Sys_Printf( " done.\n" );
777 /* return to sender */