1 /* -------------------------------------------------------------------------------
3 Copyright (C) 1999-2006 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 ------------------------------------------------------------------------------- */
32 #define LIGHTMAPS_YDNAR_C
42 /* -------------------------------------------------------------------------------
44 this file contains code that doe lightmap allocation and projection that
45 runs in the -light phase.
47 this is handled here rather than in the bsp phase for a few reasons--
48 surfaces are no longer necessarily convex polygons, patches may or may not be
49 planar or have lightmaps projected directly onto control points.
51 also, this allows lightmaps to be calculated before being allocated and stored
52 in the bsp. lightmaps that have little high-frequency information are candidates
53 for having their resolutions scaled down.
55 ------------------------------------------------------------------------------- */
59 based on WriteTGA() from imagelib.c
62 void WriteTGA24( char *filename, byte *data, int width, int height, qboolean flip )
69 /* allocate a buffer and set it up */
70 buffer = safe_malloc( width * height * 3 + 18 );
71 memset( buffer, 0, 18 );
73 buffer[ 12 ] = width & 255;
74 buffer[ 13 ] = width >> 8;
75 buffer[ 14 ] = height & 255;
76 buffer[ 15 ] = height >> 8;
80 c = (width * height * 3) + 18;
81 for( i = 18; i < c; i += 3 )
83 buffer[ i ] = data[ i - 18 + 2 ]; /* blue */
84 buffer[ i + 1 ] = data[ i - 18 + 1 ]; /* green */
85 buffer[ i + 2 ] = data[ i - 18 + 0 ]; /* red */
88 /* write it and free the buffer */
89 file = fopen( filename, "wb" );
91 Error( "Unable to open %s for writing", filename );
93 /* flip vertically? */
96 fwrite( buffer, 1, 18, file );
97 for( in = buffer + ((height - 1) * width * 3) + 18; in >= buffer; in -= (width * 3) )
98 fwrite( in, 1, (width * 3), file );
101 fwrite( buffer, 1, c, file );
112 exports the lightmaps as a list of numbered tga images
115 void ExportLightmaps( void )
118 char dirname[ 1024 ], filename[ 1024 ];
123 Sys_FPrintf( SYS_VRB, "--- ExportLightmaps ---\n");
125 /* do some path mangling */
126 strcpy( dirname, source );
127 StripExtension( dirname );
130 if( bspLightBytes == NULL )
132 Sys_Printf( "WARNING: No BSP lightmap data\n" );
136 /* make a directory for the lightmaps */
139 /* iterate through the lightmaps */
140 for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (game->lightmapSize * game->lightmapSize * 3) )
142 /* write a tga image out */
143 sprintf( filename, "%s/lightmap_%04d.tga", dirname, i );
144 Sys_Printf( "Writing %s\n", filename );
145 WriteTGA24( filename, lightmap, game->lightmapSize, game->lightmapSize, qfalse );
152 ExportLightmapsMain()
153 exports the lightmaps as a list of numbered tga images
156 int ExportLightmapsMain( int argc, char **argv )
161 Sys_Printf( "Usage: q3map -export [-v] <mapname>\n" );
165 /* do some path mangling */
166 strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
167 StripExtension( source );
168 DefaultExtension( source, ".bsp" );
171 Sys_Printf( "Loading %s\n", source );
172 LoadBSPFile( source );
174 /* export the lightmaps */
177 /* return to sender */
184 ImportLightmapsMain()
185 imports the lightmaps from a list of numbered tga images
188 int ImportLightmapsMain( int argc, char **argv )
190 int i, x, y, len, width, height;
191 char dirname[ 1024 ], filename[ 1024 ];
192 byte *lightmap, *buffer, *pixels, *in, *out;
198 Sys_Printf( "Usage: q3map -import [-v] <mapname>\n" );
202 /* do some path mangling */
203 strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
204 StripExtension( source );
205 DefaultExtension( source, ".bsp" );
208 Sys_Printf( "Loading %s\n", source );
209 LoadBSPFile( source );
212 Sys_FPrintf( SYS_VRB, "--- ImportLightmaps ---\n");
214 /* do some path mangling */
215 strcpy( dirname, source );
216 StripExtension( dirname );
219 if( bspLightBytes == NULL )
220 Error( "No lightmap data" );
222 /* make a directory for the lightmaps */
225 /* iterate through the lightmaps */
226 for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (game->lightmapSize * game->lightmapSize * 3) )
228 /* read a tga image */
229 sprintf( filename, "%s/lightmap_%04d.tga", dirname, i );
230 Sys_Printf( "Loading %s\n", filename );
232 len = vfsLoadFile( filename, (void*) &buffer, -1 );
235 Sys_Printf( "WARNING: Unable to load image %s\n", filename );
239 /* parse file into an image */
241 LoadTGABuffer( buffer, buffer + len, &pixels, &width, &height );
244 /* sanity check it */
247 Sys_Printf( "WARNING: Unable to load image %s\n", filename );
250 if( width != game->lightmapSize || height != game->lightmapSize )
251 Sys_Printf( "WARNING: Image %s is not the right size (%d, %d) != (%d, %d)\n",
252 filename, width, height, game->lightmapSize, game->lightmapSize );
254 /* copy the pixels */
256 for( y = 1; y <= game->lightmapSize; y++ )
258 out = lightmap + ((game->lightmapSize - y) * game->lightmapSize * 3);
259 for( x = 0; x < game->lightmapSize; x++, in += 4, out += 3 )
260 VectorCopy( in, out );
268 Sys_Printf( "writing %s\n", source );
269 WriteBSPFile( source );
271 /* return to sender */
277 /* -------------------------------------------------------------------------------
279 this section deals with projecting a lightmap onto a raw drawsurface
281 ------------------------------------------------------------------------------- */
284 CompareLightSurface()
285 compare function for qsort()
288 static int CompareLightSurface( const void *a, const void *b )
290 shaderInfo_t *asi, *bsi;
294 asi = surfaceInfos[ *((int*) a) ].si;
295 bsi = surfaceInfos[ *((int*) b) ].si;
303 /* compare shader names */
304 return strcmp( asi->shader, bsi->shader );
311 allocates a raw lightmap's necessary buffers
314 void FinishRawLightmap( rawLightmap_t *lm )
316 int i, j, c, size, *sc;
321 /* sort light surfaces by shader name */
322 qsort( &lightSurfaces[ lm->firstLightSurface ], lm->numLightSurfaces, sizeof( int ), CompareLightSurface );
325 lm->numLightClusters = 0;
326 for( i = 0; i < lm->numLightSurfaces; i++ )
328 /* get surface info */
329 info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ];
331 /* add surface clusters */
332 lm->numLightClusters += info->numSurfaceClusters;
335 /* allocate buffer for clusters and copy */
336 lm->lightClusters = safe_malloc( lm->numLightClusters * sizeof( *lm->lightClusters ) );
338 for( i = 0; i < lm->numLightSurfaces; i++ )
340 /* get surface info */
341 info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ];
343 /* add surface clusters */
344 for( j = 0; j < info->numSurfaceClusters; j++ )
345 lm->lightClusters[ c++ ] = surfaceClusters[ info->firstSurfaceCluster + j ];
349 lm->styles[ 0 ] = LS_NORMAL;
350 for( i = 1; i < MAX_LIGHTMAPS; i++ )
351 lm->styles[ i ] = LS_NONE;
353 /* set supersampling size */
354 lm->sw = lm->w * superSample;
355 lm->sh = lm->h * superSample;
357 /* add to super luxel count */
358 numRawSuperLuxels += (lm->sw * lm->sh);
360 /* manipulate origin/vecs for supersampling */
361 if( superSample > 1 && lm->vecs != NULL )
363 /* calc inverse supersample */
364 is = 1.0f / superSample;
366 /* scale the vectors and shift the origin */
368 /* new code that works for arbitrary supersampling values */
369 VectorMA( lm->origin, -0.5, lm->vecs[ 0 ], lm->origin );
370 VectorMA( lm->origin, -0.5, lm->vecs[ 1 ], lm->origin );
371 VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] );
372 VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] );
373 VectorMA( lm->origin, is, lm->vecs[ 0 ], lm->origin );
374 VectorMA( lm->origin, is, lm->vecs[ 1 ], lm->origin );
376 /* old code that only worked with a value of 2 */
377 VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] );
378 VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] );
379 VectorMA( lm->origin, -is, lm->vecs[ 0 ], lm->origin );
380 VectorMA( lm->origin, -is, lm->vecs[ 1 ], lm->origin );
384 /* allocate bsp lightmap storage */
385 size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float );
386 if( lm->bspLuxels[ 0 ] == NULL )
387 lm->bspLuxels[ 0 ] = safe_malloc( size );
388 memset( lm->bspLuxels[ 0 ], 0, size );
390 /* allocate radiosity lightmap storage */
393 size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float );
394 if( lm->radLuxels[ 0 ] == NULL )
395 lm->radLuxels[ 0 ] = safe_malloc( size );
396 memset( lm->radLuxels[ 0 ], 0, size );
399 /* allocate sampling lightmap storage */
400 size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
401 if( lm->superLuxels[ 0 ] == NULL )
402 lm->superLuxels[ 0 ] = safe_malloc( size );
403 memset( lm->superLuxels[ 0 ], 0, size );
405 /* allocate origin map storage */
406 size = lm->sw * lm->sh * SUPER_ORIGIN_SIZE * sizeof( float );
407 if( lm->superOrigins == NULL )
408 lm->superOrigins = safe_malloc( size );
409 memset( lm->superOrigins, 0, size );
411 /* allocate normal map storage */
412 size = lm->sw * lm->sh * SUPER_NORMAL_SIZE * sizeof( float );
413 if( lm->superNormals == NULL )
414 lm->superNormals = safe_malloc( size );
415 memset( lm->superNormals, 0, size );
417 /* allocate cluster map storage */
418 size = lm->sw * lm->sh * sizeof( int );
419 if( lm->superClusters == NULL )
420 lm->superClusters = safe_malloc( size );
421 size = lm->sw * lm->sh;
422 sc = lm->superClusters;
423 for( i = 0; i < size; i++ )
424 (*sc++) = CLUSTER_UNMAPPED;
426 /* deluxemap allocation */
429 /* allocate sampling deluxel storage */
430 size = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof( float );
431 if( lm->superDeluxels == NULL )
432 lm->superDeluxels = safe_malloc( size );
433 memset( lm->superDeluxels, 0, size );
435 /* allocate bsp deluxel storage */
436 size = lm->w * lm->h * BSP_DELUXEL_SIZE * sizeof( float );
437 if( lm->bspDeluxels == NULL )
438 lm->bspDeluxels = safe_malloc( size );
439 memset( lm->bspDeluxels, 0, size );
443 numLuxels += (lm->sw * lm->sh);
449 AddPatchToRawLightmap()
450 projects a lightmap for a patch surface
451 since lightmap calculation for surfaces is now handled in a general way (light_ydnar.c),
452 it is no longer necessary for patch verts to fall exactly on a lightmap sample
453 based on AllocateLightmapForPatch()
456 qboolean AddPatchToRawLightmap( int num, rawLightmap_t *lm )
458 bspDrawSurface_t *ds;
461 bspDrawVert_t *verts, *a, *b;
463 mesh_t src, *subdivided, *mesh;
464 float sBasis, tBasis, s, t;
465 float length, widthTable[ MAX_EXPANDED_AXIS ], heightTable[ MAX_EXPANDED_AXIS ];
468 /* patches finish a raw lightmap */
469 lm->finished = qtrue;
471 /* get surface and info */
472 ds = &bspDrawSurfaces[ num ];
473 info = &surfaceInfos[ num ];
475 /* make a temporary mesh from the drawsurf */
476 src.width = ds->patchWidth;
477 src.height = ds->patchHeight;
478 src.verts = &yDrawVerts[ ds->firstVert ];
479 //% subdivided = SubdivideMesh( src, 8, 512 );
480 subdivided = SubdivideMesh2( src, info->patchIterations );
482 /* fit it to the curve and remove colinear verts on rows/columns */
483 PutMeshOnCurve( *subdivided );
484 mesh = RemoveLinearMeshColumnsRows( subdivided );
485 FreeMesh( subdivided );
487 /* find the longest distance on each row/column */
489 memset( widthTable, 0, sizeof( widthTable ) );
490 memset( heightTable, 0, sizeof( heightTable ) );
491 for( y = 0; y < mesh->height; y++ )
493 for( x = 0; x < mesh->width; x++ )
496 if( x + 1 < mesh->width )
498 a = &verts[ (y * mesh->width) + x ];
499 b = &verts[ (y * mesh->width) + x + 1 ];
500 VectorSubtract( a->xyz, b->xyz, delta );
501 length = VectorLength( delta );
502 if( length > widthTable[ x ] )
503 widthTable[ x ] = length;
507 if( y + 1 < mesh->height )
509 a = &verts[ (y * mesh->width) + x ];
510 b = &verts[ ((y + 1) * mesh->width) + x ];
511 VectorSubtract( a->xyz, b->xyz, delta );
512 length = VectorLength( delta );
513 if( length > heightTable[ y ] )
514 heightTable[ y ] = length;
519 /* determine lightmap width */
521 for( x = 0; x < (mesh->width - 1); x++ )
522 length += widthTable[ x ];
523 lm->w = ceil( length / lm->sampleSize ) + 1;
524 if( lm->w < ds->patchWidth )
525 lm->w = ds->patchWidth;
526 if( lm->w > lm->customWidth )
527 lm->w = lm->customWidth;
528 sBasis = (float) (lm->w - 1) / (float) (ds->patchWidth - 1);
530 /* determine lightmap height */
532 for( y = 0; y < (mesh->height - 1); y++ )
533 length += heightTable[ y ];
534 lm->h = ceil( length / lm->sampleSize ) + 1;
535 if( lm->h < ds->patchHeight )
536 lm->h = ds->patchHeight;
537 if( lm->h > lm->customHeight )
538 lm->h = lm->customHeight;
539 tBasis = (float) (lm->h - 1) / (float) (ds->patchHeight - 1);
541 /* free the temporary mesh */
544 /* set the lightmap texture coordinates in yDrawVerts */
545 lm->wrap[ 0 ] = qtrue;
546 lm->wrap[ 1 ] = qtrue;
547 verts = &yDrawVerts[ ds->firstVert ];
548 for( y = 0; y < ds->patchHeight; y++ )
550 t = (tBasis * y) + 0.5f;
551 for( x = 0; x < ds->patchWidth; x++ )
553 s = (sBasis * x) + 0.5f;
554 verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 0 ] = s * superSample;
555 verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 1 ] = t * superSample;
557 if( y == 0 && !VectorCompare( verts[ x ].xyz, verts[ ((ds->patchHeight - 1) * ds->patchWidth) + x ].xyz ) )
558 lm->wrap[ 1 ] = qfalse;
561 if( !VectorCompare( verts[ (y * ds->patchWidth) ].xyz, verts[ (y * ds->patchWidth) + (ds->patchWidth - 1) ].xyz ) )
562 lm->wrap[ 0 ] = qfalse;
566 //% Sys_Printf( "wrap S: %d wrap T: %d\n", lm->wrap[ 0 ], lm->wrap[ 1 ] );
567 //% if( lm->w > (ds->lightmapWidth & 0xFF) || lm->h > (ds->lightmapHeight & 0xFF) )
568 //% Sys_Printf( "Patch lightmap: (%3d %3d) > (%3d, %3d)\n", lm->w, lm->h, ds->lightmapWidth & 0xFF, ds->lightmapHeight & 0xFF );
569 //% ds->lightmapWidth = lm->w | (ds->lightmapWidth & 0xFFFF0000);
570 //% ds->lightmapHeight = lm->h | (ds->lightmapHeight & 0xFFFF0000);
573 numPatchesLightmapped++;
582 AddSurfaceToRawLightmap()
583 projects a lightmap for a surface
584 based on AllocateLightmapForSurface()
587 qboolean AddSurfaceToRawLightmap( int num, rawLightmap_t *lm )
589 bspDrawSurface_t *ds, *ds2;
590 surfaceInfo_t *info, *info2;
591 int num2, n, i, axisNum;
592 float s, t, d, len, sampleSize;
593 vec3_t mins, maxs, origin, faxis, size, exactSize, delta, normalized, vecs[ 2 ];
595 bspDrawVert_t *verts;
598 /* get surface and info */
599 ds = &bspDrawSurfaces[ num ];
600 info = &surfaceInfos[ num ];
602 /* add the surface to the raw lightmap */
603 lightSurfaces[ numLightSurfaces++ ] = num;
604 lm->numLightSurfaces++;
606 /* does this raw lightmap already have any surfaces? */
607 if( lm->numLightSurfaces > 1 )
609 /* surface and raw lightmap must have the same lightmap projection axis */
610 if( VectorCompare( info->axis, lm->axis ) == qfalse )
613 /* match identical attributes */
614 if( info->sampleSize != lm->sampleSize ||
615 info->entityNum != lm->entityNum ||
616 info->recvShadows != lm->recvShadows ||
617 info->si->lmCustomWidth != lm->customWidth ||
618 info->si->lmCustomHeight != lm->customHeight ||
619 info->si->lmBrightness != lm->brightness ||
620 info->si->lmFilterRadius != lm->filterRadius ||
621 info->si->splotchFix != lm->splotchFix )
624 /* surface bounds must intersect with raw lightmap bounds */
625 for( i = 0; i < 3; i++ )
627 if( info->mins[ i ] > lm->maxs[ i ] )
629 if( info->maxs[ i ] < lm->mins[ i ] )
633 /* plane check (fixme: allow merging of nonplanars) */
634 if( info->si->lmMergable == qfalse )
636 if( info->plane == NULL || lm->plane == NULL )
640 for( i = 0; i < 4; i++ )
641 if( fabs( info->plane[ i ] - lm->plane[ i ] ) > EQUAL_EPSILON )
645 /* debug code hacking */
646 //% if( lm->numLightSurfaces > 1 )
651 if( info->plane == NULL )
654 /* add surface to lightmap bounds */
655 AddPointToBounds( info->mins, lm->mins, lm->maxs );
656 AddPointToBounds( info->maxs, lm->mins, lm->maxs );
658 /* check to see if this is a non-planar patch */
659 if( ds->surfaceType == MST_PATCH &&
660 lm->axis[ 0 ] == 0.0f && lm->axis[ 1 ] == 0.0f && lm->axis[ 2 ] == 0.0f )
661 return AddPatchToRawLightmap( num, lm );
663 /* start with initially requested sample size */
664 sampleSize = lm->sampleSize;
666 /* round to the lightmap resolution */
667 for( i = 0; i < 3; i++ )
669 exactSize[ i ] = lm->maxs[ i ] - lm->mins[ i ];
670 mins[ i ] = sampleSize * floor( lm->mins[ i ] / sampleSize );
671 maxs[ i ] = sampleSize * ceil( lm->maxs[ i ] / sampleSize );
672 size[ i ] = (maxs[ i ] - mins[ i ]) / sampleSize + 1.0f;
674 /* hack (god this sucks) */
675 if( size[ i ] > lm->customWidth || size[ i ] > lm->customHeight )
682 /* set actual sample size */
683 lm->actualSampleSize = sampleSize;
685 /* fixme: copy rounded mins/maxes to lightmap record? */
686 if( lm->plane == NULL )
688 VectorCopy( mins, lm->mins );
689 VectorCopy( maxs, lm->maxs );
690 VectorCopy( mins, origin );
693 /* set lightmap origin */
694 VectorCopy( lm->mins, origin );
696 /* make absolute axis */
697 faxis[ 0 ] = fabs( lm->axis[ 0 ] );
698 faxis[ 1 ] = fabs( lm->axis[ 1 ] );
699 faxis[ 2 ] = fabs( lm->axis[ 2 ] );
701 /* clear out lightmap vectors */
702 memset( vecs, 0, sizeof( vecs ) );
704 /* classify the plane (x y or z major) (ydnar: biased to z axis projection) */
705 if( faxis[ 2 ] >= faxis[ 0 ] && faxis[ 2 ] >= faxis[ 1 ] )
710 vecs[ 0 ][ 0 ] = 1.0f / sampleSize;
711 vecs[ 1 ][ 1 ] = 1.0f / sampleSize;
713 else if( faxis[ 0 ] >= faxis[ 1 ] && faxis[ 0 ] >= faxis[ 2 ] )
718 vecs[ 0 ][ 1 ] = 1.0f / sampleSize;
719 vecs[ 1 ][ 2 ] = 1.0f / sampleSize;
726 vecs[ 0 ][ 0 ] = 1.0f / sampleSize;
727 vecs[ 1 ][ 2 ] = 1.0f / sampleSize;
730 /* check for bogus axis */
731 if( faxis[ axisNum ] == 0.0f )
733 Sys_Printf( "WARNING: ProjectSurfaceLightmap: Chose a 0 valued axis\n" );
738 /* store the axis number in the lightmap */
739 lm->axisNum = axisNum;
741 /* walk the list of surfaces on this raw lightmap */
742 for( n = 0; n < lm->numLightSurfaces; n++ )
745 num2 = lightSurfaces[ lm->firstLightSurface + n ];
746 ds2 = &bspDrawSurfaces[ num2 ];
747 info2 = &surfaceInfos[ num2 ];
748 verts = &yDrawVerts[ ds2->firstVert ];
750 /* set the lightmap texture coordinates in yDrawVerts in [0, superSample * lm->customWidth] space */
751 for( i = 0; i < ds2->numVerts; i++ )
753 VectorSubtract( verts[ i ].xyz, origin, delta );
754 s = DotProduct( delta, vecs[ 0 ] ) + 0.5f;
755 t = DotProduct( delta, vecs[ 1 ] ) + 0.5f;
756 verts[ i ].lightmap[ 0 ][ 0 ] = s * superSample;
757 verts[ i ].lightmap[ 0 ][ 1 ] = t * superSample;
759 if( s > (float) lm->w || t > (float) lm->h )
761 Sys_FPrintf( SYS_VRB, "WARNING: Lightmap texture coords out of range: S %1.4f > %3d || T %1.4f > %3d\n",
762 s, lm->w, t, lm->h );
767 /* get first drawsurface */
768 num2 = lightSurfaces[ lm->firstLightSurface ];
769 ds2 = &bspDrawSurfaces[ num2 ];
770 info2 = &surfaceInfos[ num2 ];
771 verts = &yDrawVerts[ ds2->firstVert ];
773 /* calculate lightmap origin */
774 if( VectorLength( ds2->lightmapVecs[ 2 ] ) )
775 VectorCopy( ds2->lightmapVecs[ 2 ], plane );
777 VectorCopy( lm->axis, plane );
778 plane[ 3 ] = DotProduct( verts[ 0 ].xyz, plane );
780 VectorCopy( origin, lm->origin );
781 d = DotProduct( lm->origin, plane ) - plane[ 3 ];
782 d /= plane[ axisNum ];
783 lm->origin[ axisNum ] -= d;
786 VectorCopy( lm->origin, ds->lightmapOrigin );
788 /* for planar surfaces, create lightmap vectors for st->xyz conversion */
789 if( VectorLength( ds->lightmapVecs[ 2 ] ) || 1 ) /* ydnar: can't remember what exactly i was thinking here... */
791 /* allocate space for the vectors */
792 lm->vecs = safe_malloc( 3 * sizeof( vec3_t ) );
793 memset( lm->vecs, 0, 3 * sizeof( vec3_t ) );
794 VectorCopy( ds->lightmapVecs[ 2 ], lm->vecs[ 2 ] );
796 /* project stepped lightmap blocks and subtract to get planevecs */
797 for( i = 0; i < 2; i++ )
799 len = VectorNormalize( vecs[ i ], normalized );
800 VectorScale( normalized, (1.0 / len), lm->vecs[ i ] );
801 d = DotProduct( lm->vecs[ i ], plane );
802 d /= plane[ axisNum ];
803 lm->vecs[ i ][ axisNum ] -= d;
808 /* lightmap vectors are useless on a non-planar surface */
813 if( ds->surfaceType == MST_PATCH )
815 numPatchesLightmapped++;
816 if( lm->plane != NULL )
817 numPlanarPatchesLightmapped++;
821 if( lm->plane != NULL )
822 numPlanarsLightmapped++;
824 numNonPlanarsLightmapped++;
835 compare function for qsort()
838 static int CompareSurfaceInfo( const void *a, const void *b )
840 surfaceInfo_t *aInfo, *bInfo;
844 /* get surface info */
845 aInfo = &surfaceInfos[ *((int*) a) ];
846 bInfo = &surfaceInfos[ *((int*) b) ];
849 if( aInfo->model < bInfo->model )
851 else if( aInfo->model > bInfo->model )
854 /* then lightmap status */
855 if( aInfo->hasLightmap < bInfo->hasLightmap )
857 else if( aInfo->hasLightmap > bInfo->hasLightmap )
860 /* then lightmap sample size */
861 if( aInfo->sampleSize < bInfo->sampleSize )
863 else if( aInfo->sampleSize > bInfo->sampleSize )
866 /* then lightmap axis */
867 for( i = 0; i < 3; i++ )
869 if( aInfo->axis[ i ] < bInfo->axis[ i ] )
871 else if( aInfo->axis[ i ] > bInfo->axis[ i ] )
876 if( aInfo->plane == NULL && bInfo->plane != NULL )
878 else if( aInfo->plane != NULL && bInfo->plane == NULL )
880 else if( aInfo->plane != NULL && bInfo->plane != NULL )
882 for( i = 0; i < 4; i++ )
884 if( aInfo->plane[ i ] < bInfo->plane[ i ] )
886 else if( aInfo->plane[ i ] > bInfo->plane[ i ] )
891 /* then position in world */
892 for( i = 0; i < 3; i++ )
894 if( aInfo->mins[ i ] < bInfo->mins[ i ] )
896 else if( aInfo->mins[ i ] > bInfo->mins[ i ] )
900 /* these are functionally identical (this should almost never happen) */
907 SetupSurfaceLightmaps()
908 allocates lightmaps for every surface in the bsp that needs one
909 this depends on yDrawVerts being allocated
912 void SetupSurfaceLightmaps( void )
914 int i, j, k, s,num, num2;
917 bspDrawSurface_t *ds, *ds2;
918 surfaceInfo_t *info, *info2;
921 vec3_t mapSize, entityOrigin;
925 Sys_FPrintf( SYS_VRB, "--- SetupSurfaceLightmaps ---\n");
927 /* determine supersample amount */
928 if( superSample < 1 )
930 else if( superSample > 8 )
932 Sys_Printf( "WARNING: Insane supersampling amount (%d) detected.\n", superSample );
936 /* clear map bounds */
937 ClearBounds( mapMins, mapMaxs );
939 /* allocate a list of surface clusters */
940 numSurfaceClusters = 0;
941 maxSurfaceClusters = numBSPLeafSurfaces;
942 surfaceClusters = safe_malloc( maxSurfaceClusters * sizeof( *surfaceClusters ) );
943 memset( surfaceClusters, 0, maxSurfaceClusters * sizeof( *surfaceClusters ) );
945 /* allocate a list for per-surface info */
946 surfaceInfos = safe_malloc( numBSPDrawSurfaces * sizeof( *surfaceInfos ) );
947 memset( surfaceInfos, 0, numBSPDrawSurfaces * sizeof( *surfaceInfos ) );
948 for( i = 0; i < numBSPDrawSurfaces; i++ )
949 surfaceInfos[ i ].childSurfaceNum = -1;
951 /* allocate a list of surface indexes to be sorted */
952 sortSurfaces = safe_malloc( numBSPDrawSurfaces * sizeof( int ) );
953 memset( sortSurfaces, 0, numBSPDrawSurfaces * sizeof( int ) );
955 /* walk each model in the bsp */
956 for( i = 0; i < numBSPModels; i++ )
959 model = &bspModels[ i ];
961 /* walk the list of surfaces in this model and fill out the info structs */
962 for( j = 0; j < model->numBSPSurfaces; j++ )
964 /* make surface index */
965 num = model->firstBSPSurface + j;
967 /* copy index to sort list */
968 sortSurfaces[ num ] = num;
970 /* get surface and info */
971 ds = &bspDrawSurfaces[ num ];
972 info = &surfaceInfos[ num ];
974 /* set entity origin */
975 if( ds->numVerts > 0 )
976 VectorSubtract( yDrawVerts[ ds->firstVert ].xyz, bspDrawVerts[ ds->firstVert ].xyz, entityOrigin );
978 VectorClear( entityOrigin );
984 info->firstSurfaceCluster = numSurfaceClusters;
987 info->si = GetSurfaceExtraShaderInfo( num );
988 if( info->si == NULL )
989 info->si = ShaderInfoForShader( bspShaders[ ds->shaderNum ].shader );
990 info->parentSurfaceNum = GetSurfaceExtraParentSurfaceNum( num );
991 info->entityNum = GetSurfaceExtraEntityNum( num );
992 info->castShadows = GetSurfaceExtraCastShadows( num );
993 info->recvShadows = GetSurfaceExtraRecvShadows( num );
994 info->sampleSize = GetSurfaceExtraSampleSize( num );
995 info->longestCurve = GetSurfaceExtraLongestCurve( num );
996 info->patchIterations = IterationsForCurve( info->longestCurve, patchSubdivisions );
997 GetSurfaceExtraLightmapAxis( num, info->axis );
1000 if( info->parentSurfaceNum >= 0 )
1001 surfaceInfos[ info->parentSurfaceNum ].childSurfaceNum = j;
1003 /* determine surface bounds */
1004 ClearBounds( info->mins, info->maxs );
1005 for( k = 0; k < ds->numVerts; k++ )
1007 AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, mapMins, mapMaxs );
1008 AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, info->mins, info->maxs );
1011 /* find all the bsp clusters the surface falls into */
1012 for( k = 0; k < numBSPLeafs; k++ )
1015 leaf = &bspLeafs[ k ];
1018 if( leaf->mins[ 0 ] > info->maxs[ 0 ] || leaf->maxs[ 0 ] < info->mins[ 0 ] ||
1019 leaf->mins[ 1 ] > info->maxs[ 1 ] || leaf->maxs[ 1 ] < info->mins[ 1 ] ||
1020 leaf->mins[ 2 ] > info->maxs[ 2 ] || leaf->maxs[ 2 ] < info->mins[ 2 ] )
1023 /* test leaf surfaces */
1024 for( s = 0; s < leaf->numBSPLeafSurfaces; s++ )
1026 if( bspLeafSurfaces[ leaf->firstBSPLeafSurface + s ] == num )
1028 if( numSurfaceClusters >= maxSurfaceClusters )
1029 Error( "maxSurfaceClusters exceeded" );
1030 surfaceClusters[ numSurfaceClusters ] = leaf->cluster;
1031 numSurfaceClusters++;
1032 info->numSurfaceClusters++;
1037 /* determine if surface is planar */
1038 if( VectorLength( ds->lightmapVecs[ 2 ] ) > 0.0f )
1041 info->plane = safe_malloc( 4 * sizeof( float ) );
1042 VectorCopy( ds->lightmapVecs[ 2 ], info->plane );
1043 info->plane[ 3 ] = DotProduct( yDrawVerts[ ds->firstVert ].xyz, info->plane );
1046 /* determine if surface requires a lightmap */
1047 if( ds->surfaceType == MST_TRIANGLE_SOUP ||
1048 ds->surfaceType == MST_FOLIAGE ||
1049 (info->si->compileFlags & C_VERTEXLIT) )
1050 numSurfsVertexLit++;
1053 numSurfsLightmapped++;
1054 info->hasLightmap = qtrue;
1059 /* find longest map distance */
1060 VectorSubtract( mapMaxs, mapMins, mapSize );
1061 maxMapDistance = VectorLength( mapSize );
1063 /* sort the surfaces info list */
1064 qsort( sortSurfaces, numBSPDrawSurfaces, sizeof( int ), CompareSurfaceInfo );
1066 /* allocate a list of surfaces that would go into raw lightmaps */
1067 numLightSurfaces = 0;
1068 lightSurfaces = safe_malloc( numSurfsLightmapped * sizeof( int ) );
1069 memset( lightSurfaces, 0, numSurfsLightmapped * sizeof( int ) );
1071 /* allocate a list of raw lightmaps */
1072 numRawSuperLuxels = 0;
1073 numRawLightmaps = 0;
1074 rawLightmaps = safe_malloc( numSurfsLightmapped * sizeof( *rawLightmaps ) );
1075 memset( rawLightmaps, 0, numSurfsLightmapped * sizeof( *rawLightmaps ) );
1077 /* walk the list of sorted surfaces */
1078 for( i = 0; i < numBSPDrawSurfaces; i++ )
1080 /* get info and attempt early out */
1081 num = sortSurfaces[ i ];
1082 ds = &bspDrawSurfaces[ num ];
1083 info = &surfaceInfos[ num ];
1084 if( info->hasLightmap == qfalse || info->lm != NULL || info->parentSurfaceNum >= 0 )
1087 /* allocate a new raw lightmap */
1088 lm = &rawLightmaps[ numRawLightmaps ];
1092 lm->splotchFix = info->si->splotchFix;
1093 lm->firstLightSurface = numLightSurfaces;
1094 lm->numLightSurfaces = 0;
1095 lm->sampleSize = info->sampleSize;
1096 lm->actualSampleSize = info->sampleSize;
1097 lm->entityNum = info->entityNum;
1098 lm->recvShadows = info->recvShadows;
1099 lm->brightness = info->si->lmBrightness;
1100 lm->filterRadius = info->si->lmFilterRadius;
1101 VectorCopy( info->axis, lm->axis );
1102 lm->plane = info->plane;
1103 VectorCopy( info->mins, lm->mins );
1104 VectorCopy( info->maxs, lm->maxs );
1106 lm->customWidth = info->si->lmCustomWidth;
1107 lm->customHeight = info->si->lmCustomHeight;
1109 /* add the surface to the raw lightmap */
1110 AddSurfaceToRawLightmap( num, lm );
1113 /* do an exhaustive merge */
1117 /* walk the list of surfaces again */
1119 for( j = i + 1; j < numBSPDrawSurfaces && lm->finished == qfalse; j++ )
1121 /* get info and attempt early out */
1122 num2 = sortSurfaces[ j ];
1123 ds2 = &bspDrawSurfaces[ num2 ];
1124 info2 = &surfaceInfos[ num2 ];
1125 if( info2->hasLightmap == qfalse || info2->lm != NULL )
1128 /* add the surface to the raw lightmap */
1129 if( AddSurfaceToRawLightmap( num2, lm ) )
1137 lm->numLightSurfaces--;
1143 /* finish the lightmap and allocate the various buffers */
1144 FinishRawLightmap( lm );
1147 /* allocate vertex luxel storage */
1148 for( k = 0; k < MAX_LIGHTMAPS; k++ )
1150 vertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) );
1151 memset( vertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) );
1152 radVertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) );
1153 memset( radVertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) );
1156 /* emit some stats */
1157 Sys_FPrintf( SYS_VRB, "%9d surfaces\n", numBSPDrawSurfaces );
1158 Sys_FPrintf( SYS_VRB, "%9d raw lightmaps\n", numRawLightmaps );
1159 Sys_FPrintf( SYS_VRB, "%9d surfaces vertex lit\n", numSurfsVertexLit );
1160 Sys_FPrintf( SYS_VRB, "%9d surfaces lightmapped\n", numSurfsLightmapped );
1161 Sys_FPrintf( SYS_VRB, "%9d planar surfaces lightmapped\n", numPlanarsLightmapped );
1162 Sys_FPrintf( SYS_VRB, "%9d non-planar surfaces lightmapped\n", numNonPlanarsLightmapped );
1163 Sys_FPrintf( SYS_VRB, "%9d patches lightmapped\n", numPatchesLightmapped );
1164 Sys_FPrintf( SYS_VRB, "%9d planar patches lightmapped\n", numPlanarPatchesLightmapped );
1170 StitchSurfaceLightmaps()
1171 stitches lightmap edges
1172 2002-11-20 update: use this func only for stitching nonplanar patch lightmap seams
1175 #define MAX_STITCH_CANDIDATES 32
1176 #define MAX_STITCH_LUXELS 64
1178 void StitchSurfaceLightmaps( void )
1180 int i, j, x, y, x2, y2, *cluster, *cluster2,
1181 numStitched, numCandidates, numLuxels, f, fOld, start;
1182 rawLightmap_t *lm, *a, *b, *c[ MAX_STITCH_CANDIDATES ];
1183 float *luxel, *luxel2, *origin, *origin2, *normal, *normal2,
1184 sampleSize, average[ 3 ], totalColor, ootc, *luxels[ MAX_STITCH_LUXELS ];
1187 /* disabled for now */
1191 Sys_Printf( "--- StitchSurfaceLightmaps ---\n");
1195 start = I_FloatTime();
1197 /* walk the list of raw lightmaps */
1199 for( i = 0; i < numRawLightmaps; i++ )
1201 /* print pacifier */
1202 f = 10 * i / numRawLightmaps;
1206 Sys_Printf( "%i...", f );
1209 /* get lightmap a */
1210 a = &rawLightmaps[ i ];
1212 /* walk rest of lightmaps */
1214 for( j = i + 1; j < numRawLightmaps && numCandidates < MAX_STITCH_CANDIDATES; j++ )
1216 /* get lightmap b */
1217 b = &rawLightmaps[ j ];
1219 /* test bounding box */
1220 if( a->mins[ 0 ] > b->maxs[ 0 ] || a->maxs[ 0 ] < b->mins[ 0 ] ||
1221 a->mins[ 1 ] > b->maxs[ 1 ] || a->maxs[ 1 ] < b->mins[ 1 ] ||
1222 a->mins[ 2 ] > b->maxs[ 2 ] || a->maxs[ 2 ] < b->mins[ 2 ] )
1226 c[ numCandidates++ ] = b;
1230 for( y = 0; y < a->sh; y++ )
1232 for( x = 0; x < a->sw; x++ )
1234 /* ignore unmapped/unlit luxels */
1236 cluster = SUPER_CLUSTER( x, y );
1237 if( *cluster == CLUSTER_UNMAPPED )
1239 luxel = SUPER_LUXEL( 0, x, y );
1240 if( luxel[ 3 ] <= 0.0f )
1243 /* get particulars */
1244 origin = SUPER_ORIGIN( x, y );
1245 normal = SUPER_NORMAL( x, y );
1247 /* walk candidate list */
1248 for( j = 0; j < numCandidates; j++ )
1254 /* set samplesize to the smaller of the pair */
1255 sampleSize = 0.5f * (a->actualSampleSize < b->actualSampleSize ? a->actualSampleSize : b->actualSampleSize);
1257 /* test bounding box */
1258 if( origin[ 0 ] < (b->mins[ 0 ] - sampleSize) || (origin[ 0 ] > b->maxs[ 0 ] + sampleSize) ||
1259 origin[ 1 ] < (b->mins[ 1 ] - sampleSize) || (origin[ 1 ] > b->maxs[ 1 ] + sampleSize) ||
1260 origin[ 2 ] < (b->mins[ 2 ] - sampleSize) || (origin[ 2 ] > b->maxs[ 2 ] + sampleSize) )
1263 /* walk candidate luxels */
1264 VectorClear( average );
1267 for( y2 = 0; y2 < b->sh && numLuxels < MAX_STITCH_LUXELS; y2++ )
1269 for( x2 = 0; x2 < b->sw && numLuxels < MAX_STITCH_LUXELS; x2++ )
1271 /* ignore same luxels */
1272 if( a == b && abs( x - x2 ) <= 1 && abs( y - y2 ) <= 1 )
1275 /* ignore unmapped/unlit luxels */
1276 cluster2 = SUPER_CLUSTER( x2, y2 );
1277 if( *cluster2 == CLUSTER_UNMAPPED )
1279 luxel2 = SUPER_LUXEL( 0, x2, y2 );
1280 if( luxel2[ 3 ] <= 0.0f )
1283 /* get particulars */
1284 origin2 = SUPER_ORIGIN( x2, y2 );
1285 normal2 = SUPER_NORMAL( x2, y2 );
1288 if( DotProduct( normal, normal2 ) < 0.5f )
1292 if( fabs( origin[ 0 ] - origin2[ 0 ] ) > sampleSize ||
1293 fabs( origin[ 1 ] - origin2[ 1 ] ) > sampleSize ||
1294 fabs( origin[ 2 ] - origin2[ 2 ] ) > sampleSize )
1298 //% VectorSet( luxel2, 255, 0, 255 );
1299 luxels[ numLuxels++ ] = luxel2;
1300 VectorAdd( average, luxel2, average );
1301 totalColor += luxel2[ 3 ];
1306 if( numLuxels == 0 )
1310 ootc = 1.0f / totalColor;
1311 VectorScale( average, ootc, luxel );
1319 /* emit statistics */
1320 Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) );
1321 Sys_FPrintf( SYS_VRB, "%9d luxels stitched\n", numStitched );
1328 compares two surface lightmaps' bsp luxels, ignoring occluded luxels
1331 #define SOLID_EPSILON 0.0625
1332 #define LUXEL_TOLERANCE 0.0025
1333 #define LUXEL_COLOR_FRAC 0.001302083 /* 1 / 3 / 256 */
1335 static qboolean CompareBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum )
1339 double delta, total, rd, gd, bd;
1340 float *aLuxel, *bLuxel;
1343 /* styled lightmaps will never be collapsed to non-styled lightmaps when there is _minlight */
1344 if( (minLight[ 0 ] || minLight[ 1 ] || minLight[ 2 ]) &&
1345 ((aNum == 0 && bNum != 0) || (aNum != 0 && bNum == 0)) )
1349 if( a->customWidth != b->customWidth || a->customHeight != b->customHeight ||
1350 a->brightness != b->brightness ||
1351 a->solid[ aNum ] != b->solid[ bNum ] ||
1352 a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL )
1355 /* compare solid color lightmaps */
1356 if( a->solid[ aNum ] && b->solid[ bNum ] )
1359 rd = fabs( a->solidColor[ aNum ][ 0 ] - b->solidColor[ bNum ][ 0 ] );
1360 gd = fabs( a->solidColor[ aNum ][ 1 ] - b->solidColor[ bNum ][ 1 ] );
1361 bd = fabs( a->solidColor[ aNum ][ 2 ] - b->solidColor[ bNum ][ 2 ] );
1364 if( rd > SOLID_EPSILON || gd > SOLID_EPSILON|| bd > SOLID_EPSILON )
1371 /* compare nonsolid lightmaps */
1372 if( a->w != b->w || a->h != b->h )
1375 /* compare luxels */
1378 for( y = 0; y < a->h; y++ )
1380 for( x = 0; x < a->w; x++ )
1382 /* increment total */
1386 lm = a; aLuxel = BSP_LUXEL( aNum, x, y );
1387 lm = b; bLuxel = BSP_LUXEL( bNum, x, y );
1389 /* ignore unused luxels */
1390 if( aLuxel[ 0 ] < 0 || bLuxel[ 0 ] < 0 )
1394 rd = fabs( aLuxel[ 0 ] - bLuxel[ 0 ] );
1395 gd = fabs( aLuxel[ 1 ] - bLuxel[ 1 ] );
1396 bd = fabs( aLuxel[ 2 ] - bLuxel[ 2 ] );
1398 /* 2003-09-27: compare individual luxels */
1399 if( rd > 3.0 || gd > 3.0 || bd > 3.0 )
1402 /* compare (fixme: take into account perceptual differences) */
1403 delta += rd * LUXEL_COLOR_FRAC;
1404 delta += gd * LUXEL_COLOR_FRAC;
1405 delta += bd * LUXEL_COLOR_FRAC;
1407 /* is the change too high? */
1408 if( total > 0.0 && ((delta / total) > LUXEL_TOLERANCE) )
1413 /* made it this far, they must be identical (or close enough) */
1421 merges two surface lightmaps' bsp luxels, overwriting occluded luxels
1424 static qboolean MergeBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum )
1428 float luxel[ 3 ], *aLuxel, *bLuxel;
1432 if( a->customWidth != b->customWidth || a->customHeight != b->customHeight ||
1433 a->brightness != b->brightness ||
1434 a->solid[ aNum ] != b->solid[ bNum ] ||
1435 a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL )
1438 /* compare solid lightmaps */
1439 if( a->solid[ aNum ] && b->solid[ bNum ] )
1442 VectorAdd( a->solidColor[ aNum ], b->solidColor[ bNum ], luxel );
1443 VectorScale( luxel, 0.5f, luxel );
1446 VectorCopy( luxel, a->solidColor[ aNum ] );
1447 VectorCopy( luxel, b->solidColor[ bNum ] );
1449 /* return to sender */
1453 /* compare nonsolid lightmaps */
1454 if( a->w != b->w || a->h != b->h )
1458 for( y = 0; y < a->h; y++ )
1460 for( x = 0; x < a->w; x++ )
1463 lm = a; aLuxel = BSP_LUXEL( aNum, x, y );
1464 lm = b; bLuxel = BSP_LUXEL( bNum, x, y );
1466 /* handle occlusion mismatch */
1467 if( aLuxel[ 0 ] < 0.0f )
1468 VectorCopy( bLuxel, aLuxel );
1469 else if( bLuxel[ 0 ] < 0.0f )
1470 VectorCopy( aLuxel, bLuxel );
1474 VectorAdd( aLuxel, bLuxel, luxel );
1475 VectorScale( luxel, 0.5f, luxel );
1477 /* debugging code */
1478 //% luxel[ 2 ] += 64.0f;
1481 VectorCopy( luxel, aLuxel );
1482 VectorCopy( luxel, bLuxel );
1495 determines if a single luxel is can be approximated with the interpolated vertex rgba
1498 static qboolean ApproximateLuxel( rawLightmap_t *lm, bspDrawVert_t *dv )
1500 int i, x, y, d, lightmapNum;
1502 vec3_t color, vertexColor;
1503 byte cb[ 4 ], vcb[ 4 ];
1506 /* find luxel xy coords */
1507 x = dv->lightmap[ 0 ][ 0 ] / superSample;
1508 y = dv->lightmap[ 0 ][ 1 ] / superSample;
1511 else if( x >= lm->w )
1515 else if( y >= lm->h )
1519 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
1522 if( lm->styles[ lightmapNum ] == LS_NONE )
1526 luxel = BSP_LUXEL( lightmapNum, x, y );
1528 /* ignore occluded luxels */
1529 if( luxel[ 0 ] < 0.0f || luxel[ 1 ] < 0.0f || luxel[ 2 ] < 0.0f )
1532 /* copy, set min color and compare */
1533 VectorCopy( luxel, color );
1534 VectorCopy( dv->color[ 0 ], vertexColor );
1536 /* styles are not affected by minlight */
1537 if( lightmapNum == 0 )
1539 for( i = 0; i < 3; i++ )
1542 if( color[ i ] < minLight[ i ] )
1543 color[ i ] = minLight[ i ];
1544 if( vertexColor[ i ] < minLight[ i ] ) /* note NOT minVertexLight */
1545 vertexColor[ i ] = minLight[ i ];
1550 ColorToBytes( color, cb, 1.0f );
1551 ColorToBytes( vertexColor, vcb, 1.0f );
1554 for( i = 0; i < 3; i++ )
1556 d = cb[ i ] - vcb[ i ];
1559 if( d > approximateTolerance )
1564 /* close enough for the girls i date */
1571 ApproximateTriangle()
1572 determines if a single triangle can be approximated with vertex rgba
1575 static qboolean ApproximateTriangle_r( rawLightmap_t *lm, bspDrawVert_t *dv[ 3 ] )
1577 bspDrawVert_t mid, *dv2[ 3 ];
1581 /* approximate the vertexes */
1582 if( ApproximateLuxel( lm, dv[ 0 ] ) == qfalse )
1584 if( ApproximateLuxel( lm, dv[ 1 ] ) == qfalse )
1586 if( ApproximateLuxel( lm, dv[ 2 ] ) == qfalse )
1589 /* subdivide calc */
1592 float dx, dy, dist, maxDist;
1595 /* find the longest edge and split it */
1598 for( i = 0; i < 3; i++ )
1600 dx = dv[ i ]->lightmap[ 0 ][ 0 ] - dv[ (i + 1) % 3 ]->lightmap[ 0 ][ 0 ];
1601 dy = dv[ i ]->lightmap[ 0 ][ 1 ] - dv[ (i + 1) % 3 ]->lightmap[ 0 ][ 1 ];
1602 dist = sqrt( (dx * dx) + (dy * dy) );
1603 if( dist > maxDist )
1610 /* try to early out */
1611 if( i < 0 || maxDist < subdivideThreshold )
1615 /* split the longest edge and map it */
1616 LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid );
1617 if( ApproximateLuxel( lm, &mid ) == qfalse )
1620 /* recurse to first triangle */
1621 VectorCopy( dv, dv2 );
1623 if( ApproximateTriangle_r( lm, dv2 ) == qfalse )
1626 /* recurse to second triangle */
1627 VectorCopy( dv, dv2 );
1628 dv2[ (max + 1) % 3 ] = ∣
1629 return ApproximateTriangle_r( lm, dv2 );
1635 ApproximateLightmap()
1636 determines if a raw lightmap can be approximated sufficiently with vertex colors
1639 static qboolean ApproximateLightmap( rawLightmap_t *lm )
1641 int n, num, i, x, y, pw[ 5 ], r;
1642 bspDrawSurface_t *ds;
1643 surfaceInfo_t *info;
1644 mesh_t src, *subdivided, *mesh;
1645 bspDrawVert_t *verts, *dv[ 3 ];
1646 qboolean approximated;
1649 /* approximating? */
1650 if( approximateTolerance <= 0 )
1653 /* test for jmonroe */
1655 /* don't approx lightmaps with styled twins */
1656 if( lm->numStyledTwins > 0 )
1659 /* don't approx lightmaps with styles */
1660 for( i = 1; i < MAX_LIGHTMAPS; i++ )
1662 if( lm->styles[ i ] != LS_NONE )
1667 /* assume reduced until shadow detail is found */
1668 approximated = qtrue;
1670 /* walk the list of surfaces on this raw lightmap */
1671 for( n = 0; n < lm->numLightSurfaces; n++ )
1674 num = lightSurfaces[ lm->firstLightSurface + n ];
1675 ds = &bspDrawSurfaces[ num ];
1676 info = &surfaceInfos[ num ];
1678 /* assume not-reduced initially */
1679 info->approximated = qfalse;
1681 /* bail if lightmap doesn't match up */
1682 if( info->lm != lm )
1685 /* bail if not vertex lit */
1686 if( info->si->noVertexLight )
1689 /* assume that surfaces whose bounding boxes is smaller than 2x samplesize will be forced to vertex */
1690 if( (info->maxs[ 0 ] - info->mins[ 0 ]) <= (2.0f * info->sampleSize) &&
1691 (info->maxs[ 1 ] - info->mins[ 1 ]) <= (2.0f * info->sampleSize) &&
1692 (info->maxs[ 2 ] - info->mins[ 2 ]) <= (2.0f * info->sampleSize) )
1694 info->approximated = qtrue;
1695 numSurfsVertexForced++;
1699 /* handle the triangles */
1700 switch( ds->surfaceType )
1704 verts = yDrawVerts + ds->firstVert;
1706 /* map the triangles */
1707 info->approximated = qtrue;
1708 for( i = 0; i < ds->numIndexes && info->approximated; i += 3 )
1710 dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ];
1711 dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ];
1712 dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ];
1713 info->approximated = ApproximateTriangle_r( lm, dv );
1718 /* make a mesh from the drawsurf */
1719 src.width = ds->patchWidth;
1720 src.height = ds->patchHeight;
1721 src.verts = &yDrawVerts[ ds->firstVert ];
1722 //% subdivided = SubdivideMesh( src, 8, 512 );
1723 subdivided = SubdivideMesh2( src, info->patchIterations );
1725 /* fit it to the curve and remove colinear verts on rows/columns */
1726 PutMeshOnCurve( *subdivided );
1727 mesh = RemoveLinearMeshColumnsRows( subdivided );
1728 FreeMesh( subdivided );
1731 verts = mesh->verts;
1733 /* map the mesh quads */
1734 info->approximated = qtrue;
1735 for( y = 0; y < (mesh->height - 1) && info->approximated; y++ )
1737 for( x = 0; x < (mesh->width - 1) && info->approximated; x++ )
1740 pw[ 0 ] = x + (y * mesh->width);
1741 pw[ 1 ] = x + ((y + 1) * mesh->width);
1742 pw[ 2 ] = x + 1 + ((y + 1) * mesh->width);
1743 pw[ 3 ] = x + 1 + (y * mesh->width);
1744 pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */
1749 /* get drawverts and map first triangle */
1750 dv[ 0 ] = &verts[ pw[ r + 0 ] ];
1751 dv[ 1 ] = &verts[ pw[ r + 1 ] ];
1752 dv[ 2 ] = &verts[ pw[ r + 2 ] ];
1753 info->approximated = ApproximateTriangle_r( lm, dv );
1755 /* get drawverts and map second triangle */
1756 dv[ 0 ] = &verts[ pw[ r + 0 ] ];
1757 dv[ 1 ] = &verts[ pw[ r + 2 ] ];
1758 dv[ 2 ] = &verts[ pw[ r + 3 ] ];
1759 if( info->approximated )
1760 info->approximated = ApproximateTriangle_r( lm, dv );
1773 if( info->approximated == qfalse )
1774 approximated = qfalse;
1776 numSurfsVertexApproximated++;
1780 return approximated;
1786 TestOutLightmapStamp()
1787 tests a stamp on a given lightmap for validity
1790 static qboolean TestOutLightmapStamp( rawLightmap_t *lm, int lightmapNum, outLightmap_t *olm, int x, int y )
1792 int sx, sy, ox, oy, offset;
1797 if( x < 0 || y < 0 || (x + lm->w) > olm->customWidth || (y + lm->h) > olm->customHeight )
1800 /* solid lightmaps test a 1x1 stamp */
1801 if( lm->solid[ lightmapNum ] )
1803 offset = (y * olm->customWidth) + x;
1804 if( olm->lightBits[ offset >> 3 ] & (1 << (offset & 7)) )
1809 /* test the stamp */
1810 for( sy = 0; sy < lm->h; sy++ )
1812 for( sx = 0; sx < lm->w; sx++ )
1815 luxel = BSP_LUXEL( lightmapNum, sx, sy );
1816 if( luxel[ 0 ] < 0.0f )
1819 /* get bsp lightmap coords and test */
1822 offset = (oy * olm->customWidth) + ox;
1823 if( olm->lightBits[ offset >> 3 ] & (1 << (offset & 7)) )
1828 /* stamp is empty */
1836 sets up an output lightmap
1839 static void SetupOutLightmap( rawLightmap_t *lm, outLightmap_t *olm )
1842 if( lm == NULL || olm == NULL )
1845 /* is this a "normal" bsp-stored lightmap? */
1846 if( (lm->customWidth == game->lightmapSize && lm->customHeight == game->lightmapSize) || externalLightmaps )
1848 olm->lightmapNum = numBSPLightmaps;
1851 /* lightmaps are interleaved with light direction maps */
1856 olm->lightmapNum = -3;
1858 /* set external lightmap number */
1859 olm->extLightmapNum = -1;
1862 olm->numLightmaps = 0;
1863 olm->customWidth = lm->customWidth;
1864 olm->customHeight = lm->customHeight;
1865 olm->freeLuxels = olm->customWidth * olm->customHeight;
1866 olm->numShaders = 0;
1868 /* allocate buffers */
1869 olm->lightBits = safe_malloc( (olm->customWidth * olm->customHeight / 8) + 8 );
1870 memset( olm->lightBits, 0, (olm->customWidth * olm->customHeight / 8) + 8 );
1871 olm->bspLightBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 );
1872 memset( olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3 );
1875 olm->bspDirBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 );
1876 memset( olm->bspDirBytes, 0, olm->customWidth * olm->customHeight * 3 );
1884 for a given surface lightmap, find output lightmap pages and positions for it
1887 static void FindOutLightmaps( rawLightmap_t *lm )
1889 int i, j, lightmapNum, xMax, yMax, x, y, sx, sy, ox, oy, offset, temp;
1891 surfaceInfo_t *info;
1892 float *luxel, *deluxel;
1893 vec3_t color, direction;
1898 /* set default lightmap number (-3 = LIGHTMAP_BY_VERTEX) */
1899 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
1900 lm->outLightmapNums[ lightmapNum ] = -3;
1902 /* can this lightmap be approximated with vertex color? */
1903 if( ApproximateLightmap( lm ) )
1907 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
1910 if( lm->styles[ lightmapNum ] == LS_NONE )
1913 /* don't store twinned lightmaps */
1914 if( lm->twins[ lightmapNum ] != NULL )
1917 /* if this is a styled lightmap, try some normalized locations first */
1919 if( lightmapNum > 0 && outLightmaps != NULL )
1922 for( j = 0; j < 2; j++ )
1924 /* try identical position */
1925 for( i = 0; i < numOutLightmaps; i++ )
1927 /* get the output lightmap */
1928 olm = &outLightmaps[ i ];
1930 /* simple early out test */
1931 if( olm->freeLuxels < lm->used )
1934 /* don't store non-custom raw lightmaps on custom bsp lightmaps */
1935 if( olm->customWidth != lm->customWidth ||
1936 olm->customHeight != lm->customHeight )
1942 x = lm->lightmapX[ 0 ];
1943 y = lm->lightmapY[ 0 ];
1944 ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y );
1950 for( sy = -1; sy <= 1; sy++ )
1952 for( sx = -1; sx <= 1; sx++ )
1954 x = lm->lightmapX[ 0 ] + sx * (olm->customWidth >> 1); //% lm->w;
1955 y = lm->lightmapY[ 0 ] + sy * (olm->customHeight >> 1); //% lm->h;
1956 ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y );
1976 /* try normal placement algorithm */
1983 /* walk the list of lightmap pages */
1984 for( i = 0; i < numOutLightmaps; i++ )
1986 /* get the output lightmap */
1987 olm = &outLightmaps[ i ];
1989 /* simple early out test */
1990 if( olm->freeLuxels < lm->used )
1993 /* don't store non-custom raw lightmaps on custom bsp lightmaps */
1994 if( olm->customWidth != lm->customWidth ||
1995 olm->customHeight != lm->customHeight )
1999 if( lm->solid[ lightmapNum ] )
2001 xMax = olm->customWidth;
2002 yMax = olm->customHeight;
2006 xMax = (olm->customWidth - lm->w) + 1;
2007 yMax = (olm->customHeight - lm->h) + 1;
2010 /* walk the origin around the lightmap */
2011 for( y = 0; y < yMax; y++ )
2013 for( x = 0; x < xMax; x++ )
2015 /* find a fine tract of lauhnd */
2016 ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y );
2038 /* allocate two new output lightmaps */
2039 numOutLightmaps += 2;
2040 olm = safe_malloc( numOutLightmaps * sizeof( outLightmap_t ) );
2041 if( outLightmaps != NULL && numOutLightmaps > 2 )
2043 memcpy( olm, outLightmaps, (numOutLightmaps - 2) * sizeof( outLightmap_t ) );
2044 free( outLightmaps );
2048 /* initialize both out lightmaps */
2049 SetupOutLightmap( lm, &outLightmaps[ numOutLightmaps - 2 ] );
2050 SetupOutLightmap( lm, &outLightmaps[ numOutLightmaps - 1 ] );
2052 /* set out lightmap */
2053 i = numOutLightmaps - 2;
2054 olm = &outLightmaps[ i ];
2056 /* set stamp xy origin to the first surface lightmap */
2057 if( lightmapNum > 0 )
2059 x = lm->lightmapX[ 0 ];
2060 y = lm->lightmapY[ 0 ];
2064 /* if this is a style-using lightmap, it must be exported */
2065 if( lightmapNum > 0 && game->load != LoadRBSPFile )
2066 olm->extLightmapNum = 0;
2068 /* add the surface lightmap to the bsp lightmap */
2069 lm->outLightmapNums[ lightmapNum ] = i;
2070 lm->lightmapX[ lightmapNum ] = x;
2071 lm->lightmapY[ lightmapNum ] = y;
2072 olm->numLightmaps++;
2075 for( i = 0; i < lm->numLightSurfaces; i++ )
2077 /* get surface info */
2078 info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ];
2080 /* test for shader */
2081 for( j = 0; j < olm->numShaders; j++ )
2083 if( olm->shaders[ j ] == info->si )
2087 /* if it doesn't exist, add it */
2088 if( j >= olm->numShaders && olm->numShaders < MAX_LIGHTMAP_SHADERS )
2090 olm->shaders[ olm->numShaders ] = info->si;
2092 numLightmapShaders++;
2097 if( lm->solid[ lightmapNum ] )
2108 /* mark the bits used */
2109 for( y = 0; y < yMax; y++ )
2111 for( x = 0; x < xMax; x++ )
2114 luxel = BSP_LUXEL( lightmapNum, x, y );
2115 deluxel = BSP_DELUXEL( x, y );
2116 if( luxel[ 0 ] < 0.0f && !lm->solid[ lightmapNum ])
2119 /* set minimum light */
2120 if( lm->solid[ lightmapNum ] )
2123 VectorSet( color, 255.0f, 0.0f, 0.0f );
2125 VectorCopy( lm->solidColor[ lightmapNum ], color );
2128 VectorCopy( luxel, color );
2130 /* styles are not affected by minlight */
2131 if( lightmapNum == 0 )
2133 for( i = 0; i < 3; i++ )
2135 if( color[ i ] < minLight[ i ] )
2136 color[ i ] = minLight[ i ];
2140 /* get bsp lightmap coords */
2141 ox = x + lm->lightmapX[ lightmapNum ];
2142 oy = y + lm->lightmapY[ lightmapNum ];
2143 offset = (oy * olm->customWidth) + ox;
2145 /* flag pixel as used */
2146 olm->lightBits[ offset >> 3 ] |= (1 << (offset & 7));
2150 pixel = olm->bspLightBytes + (((oy * olm->customWidth) + ox) * 3);
2151 ColorToBytes( color, pixel, lm->brightness );
2153 /* store direction */
2156 /* normalize average light direction */
2157 if( VectorNormalize( deluxel, direction ) )
2159 /* encode [-1,1] in [0,255] */
2160 pixel = olm->bspDirBytes + (((oy * olm->customWidth) + ox) * 3);
2161 for( i = 0; i < 3; i++ )
2163 temp = (direction[ i ] + 1.0f) * 127.5f;
2166 else if( temp > 255 )
2181 CompareRawLightmap()
2182 compare function for qsort()
2185 static int CompareRawLightmap( const void *a, const void *b )
2187 rawLightmap_t *alm, *blm;
2188 surfaceInfo_t *aInfo, *bInfo;
2193 alm = &rawLightmaps[ *((int*) a) ];
2194 blm = &rawLightmaps[ *((int*) b) ];
2196 /* get min number of surfaces */
2197 min = (alm->numLightSurfaces < blm->numLightSurfaces ? alm->numLightSurfaces : blm->numLightSurfaces);
2200 for( i = 0; i < min; i++ )
2202 /* get surface info */
2203 aInfo = &surfaceInfos[ lightSurfaces[ alm->firstLightSurface + i ] ];
2204 bInfo = &surfaceInfos[ lightSurfaces[ blm->firstLightSurface + i ] ];
2206 /* compare shader names */
2207 diff = strcmp( aInfo->si->shader, bInfo->si->shader );
2212 /* test style count */
2214 for( i = 0; i < MAX_LIGHTMAPS; i++ )
2215 diff += blm->styles[ i ] - alm->styles[ i ];
2220 diff = (blm->w * blm->h) - (alm->w * alm->h);
2224 /* must be equivalent */
2231 StoreSurfaceLightmaps()
2232 stores the surface lightmaps into the bsp as byte rgb triplets
2235 void StoreSurfaceLightmaps( void )
2237 int i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples;
2238 int style, size, lightmapNum, lightmapNum2;
2239 float *normal, *luxel, *bspLuxel, *bspLuxel2, *radLuxel, samples, occludedSamples;
2240 vec3_t sample, occludedSample, dirSample, colorMins, colorMaxs;
2241 float *deluxel, *bspDeluxel, *bspDeluxel2;
2243 int numUsed, numTwins, numTwinLuxels, numStored;
2244 float lmx, lmy, efficiency;
2246 bspDrawSurface_t *ds, *parent, dsTemp;
2247 surfaceInfo_t *info;
2248 rawLightmap_t *lm, *lm2;
2250 bspDrawVert_t *dv, *ydv, *dvParent;
2251 char dirname[ 1024 ], filename[ 1024 ];
2253 char lightmapName[ 128 ];
2254 char *rgbGenValues[ 256 ];
2255 char *alphaGenValues[ 256 ];
2259 Sys_Printf( "--- StoreSurfaceLightmaps ---\n");
2262 strcpy( dirname, source );
2263 StripExtension( dirname );
2264 memset( rgbGenValues, 0, sizeof( rgbGenValues ) );
2265 memset( alphaGenValues, 0, sizeof( alphaGenValues ) );
2267 /* -----------------------------------------------------------------
2268 average the sampled luxels into the bsp luxels
2269 ----------------------------------------------------------------- */
2272 Sys_FPrintf( SYS_VRB, "Subsampling..." );
2274 /* walk the list of raw lightmaps */
2278 numSolidLightmaps = 0;
2279 for( i = 0; i < numRawLightmaps; i++ )
2282 lm = &rawLightmaps[ i ];
2284 /* walk individual lightmaps */
2285 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2288 if( lm->superLuxels[ lightmapNum ] == NULL )
2291 /* allocate bsp luxel storage */
2292 if( lm->bspLuxels[ lightmapNum ] == NULL )
2294 size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float );
2295 lm->bspLuxels[ lightmapNum ] = safe_malloc( size );
2296 memset( lm->bspLuxels[ lightmapNum ], 0, size );
2299 /* allocate radiosity lightmap storage */
2302 size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float );
2303 if( lm->radLuxels[ lightmapNum ] == NULL )
2304 lm->radLuxels[ lightmapNum ] = safe_malloc( size );
2305 memset( lm->radLuxels[ lightmapNum ], 0, size );
2308 /* average supersampled luxels */
2309 for( y = 0; y < lm->h; y++ )
2311 for( x = 0; x < lm->w; x++ )
2315 occludedSamples = 0.0f;
2317 VectorClear( sample );
2318 VectorClear( occludedSample );
2319 VectorClear( dirSample );
2320 for( ly = 0; ly < superSample; ly++ )
2322 for( lx = 0; lx < superSample; lx++ )
2325 sx = x * superSample + lx;
2326 sy = y * superSample + ly;
2327 luxel = SUPER_LUXEL( lightmapNum, sx, sy );
2328 deluxel = SUPER_DELUXEL( sx, sy );
2329 normal = SUPER_NORMAL( sx, sy );
2330 cluster = SUPER_CLUSTER( sx, sy );
2332 /* sample deluxemap */
2333 if( deluxemap && lightmapNum == 0 )
2334 VectorAdd( dirSample, deluxel, dirSample );
2336 /* keep track of used/occluded samples */
2337 if( *cluster != CLUSTER_UNMAPPED )
2340 /* handle lightmap border? */
2341 if( lightmapBorder && (sx == 0 || sx == (lm->sw - 1) || sy == 0 || sy == (lm->sh - 1) ) && luxel[ 3 ] > 0.0f )
2343 VectorSet( sample, 255.0f, 0.0f, 0.0f );
2348 else if( debug && *cluster < 0 )
2350 if( *cluster == CLUSTER_UNMAPPED )
2351 VectorSet( luxel, 255, 204, 0 );
2352 else if( *cluster == CLUSTER_OCCLUDED )
2353 VectorSet( luxel, 255, 0, 255 );
2354 else if( *cluster == CLUSTER_FLOODED )
2355 VectorSet( luxel, 0, 32, 255 );
2356 VectorAdd( occludedSample, luxel, occludedSample );
2357 occludedSamples += 1.0f;
2360 /* normal luxel handling */
2361 else if( luxel[ 3 ] > 0.0f )
2363 /* handle lit or flooded luxels */
2364 if( *cluster > 0 || *cluster == CLUSTER_FLOODED )
2366 VectorAdd( sample, luxel, sample );
2367 samples += luxel[ 3 ];
2370 /* handle occluded or unmapped luxels */
2373 VectorAdd( occludedSample, luxel, occludedSample );
2374 occludedSamples += luxel[ 3 ];
2377 /* handle style debugging */
2378 if( debug && lightmapNum > 0 && x < 2 && y < 2 )
2380 VectorCopy( debugColors[ 0 ], sample );
2387 /* only use occluded samples if necessary */
2388 if( samples <= 0.0f )
2390 VectorCopy( occludedSample, sample );
2391 samples = occludedSamples;
2395 luxel = SUPER_LUXEL( lightmapNum, x, y );
2396 deluxel = SUPER_DELUXEL( x, y );
2398 /* store light direction */
2399 if( deluxemap && lightmapNum == 0 )
2400 VectorCopy( dirSample, deluxel );
2402 /* store the sample back in super luxels */
2403 if( samples > 0.01f )
2405 VectorScale( sample, (1.0f / samples), luxel );
2409 /* if any samples were mapped in any way, store ambient color */
2410 else if( mappedSamples > 0 )
2412 if( lightmapNum == 0 )
2413 VectorCopy( ambientColor, luxel );
2415 VectorClear( luxel );
2419 /* store a bogus value to be fixed later */
2422 VectorClear( luxel );
2430 ClearBounds( colorMins, colorMaxs );
2432 /* clean up and store into bsp luxels */
2433 for( y = 0; y < lm->h; y++ )
2435 for( x = 0; x < lm->w; x++ )
2438 luxel = SUPER_LUXEL( lightmapNum, x, y );
2439 deluxel = SUPER_DELUXEL( x, y );
2441 /* copy light direction */
2442 if( deluxemap && lightmapNum == 0 )
2443 VectorCopy( deluxel, dirSample );
2445 /* is this a valid sample? */
2446 if( luxel[ 3 ] > 0.0f )
2448 VectorCopy( luxel, sample );
2449 samples = luxel[ 3 ];
2453 /* fix negative samples */
2454 for( j = 0; j < 3; j++ )
2456 if( sample[ j ] < 0.0f )
2462 /* nick an average value from the neighbors */
2463 VectorClear( sample );
2464 VectorClear( dirSample );
2467 /* fixme: why is this disabled?? */
2468 for( sy = (y - 1); sy <= (y + 1); sy++ )
2470 if( sy < 0 || sy >= lm->h )
2473 for( sx = (x - 1); sx <= (x + 1); sx++ )
2475 if( sx < 0 || sx >= lm->w || (sx == x && sy == y) )
2478 /* get neighbor's particulars */
2479 luxel = SUPER_LUXEL( lightmapNum, sx, sy );
2480 if( luxel[ 3 ] < 0.0f )
2482 VectorAdd( sample, luxel, sample );
2483 samples += luxel[ 3 ];
2488 if( samples == 0.0f )
2490 VectorSet( sample, -1.0f, -1.0f, -1.0f );
2498 /* fix negative samples */
2499 for( j = 0; j < 3; j++ )
2501 if( sample[ j ] < 0.0f )
2507 /* scale the sample */
2508 VectorScale( sample, (1.0f / samples), sample );
2510 /* store the sample in the radiosity luxels */
2513 radLuxel = RAD_LUXEL( lightmapNum, x, y );
2514 VectorCopy( sample, radLuxel );
2516 /* if only storing bounced light, early out here */
2517 if( bounceOnly && !bouncing )
2521 /* store the sample in the bsp luxels */
2522 bspLuxel = BSP_LUXEL( lightmapNum, x, y );
2523 bspDeluxel = BSP_DELUXEL( x, y );
2525 VectorAdd( bspLuxel, sample, bspLuxel );
2526 if( deluxemap && lightmapNum == 0 )
2527 VectorAdd( bspDeluxel, dirSample, bspDeluxel );
2529 /* add color to bounds for solid checking */
2530 if( samples > 0.0f )
2531 AddPointToBounds( bspLuxel, colorMins, colorMaxs );
2535 /* set solid color */
2536 lm->solid[ lightmapNum ] = qfalse;
2537 VectorAdd( colorMins, colorMaxs, lm->solidColor[ lightmapNum ] );
2538 VectorScale( lm->solidColor[ lightmapNum ], 0.5f, lm->solidColor[ lightmapNum ] );
2540 /* nocollapse prevents solid lightmaps */
2541 if( noCollapse == qfalse )
2543 /* check solid color */
2544 VectorSubtract( colorMaxs, colorMins, sample );
2545 if( (sample[ 0 ] <= SOLID_EPSILON && sample[ 1 ] <= SOLID_EPSILON && sample[ 2 ] <= SOLID_EPSILON) ||
2546 (lm->w <= 2 && lm->h <= 2) ) /* small lightmaps get forced to solid color */
2549 VectorCopy( colorMins, lm->solidColor[ lightmapNum ] );
2550 lm->solid[ lightmapNum ] = qtrue;
2551 numSolidLightmaps++;
2554 /* if all lightmaps aren't solid, then none of them are solid */
2555 if( lm->solid[ lightmapNum ] != lm->solid[ 0 ] )
2557 for( y = 0; y < MAX_LIGHTMAPS; y++ )
2559 if( lm->solid[ y ] )
2560 numSolidLightmaps--;
2561 lm->solid[ y ] = qfalse;
2566 /* wrap bsp luxels if necessary */
2569 for( y = 0; y < lm->h; y++ )
2571 bspLuxel = BSP_LUXEL( lightmapNum, 0, y );
2572 bspLuxel2 = BSP_LUXEL( lightmapNum, lm->w - 1, y );
2573 VectorAdd( bspLuxel, bspLuxel2, bspLuxel );
2574 VectorScale( bspLuxel, 0.5f, bspLuxel );
2575 VectorCopy( bspLuxel, bspLuxel2 );
2576 if( deluxemap && lightmapNum == 0 )
2578 bspDeluxel = BSP_DELUXEL( 0, y );
2579 bspDeluxel2 = BSP_DELUXEL( lm->w - 1, y );
2580 VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel );
2581 VectorScale( bspDeluxel, 0.5f, bspDeluxel );
2582 VectorCopy( bspDeluxel, bspDeluxel2 );
2588 for( x = 0; x < lm->w; x++ )
2590 bspLuxel = BSP_LUXEL( lightmapNum, x, 0 );
2591 bspLuxel2 = BSP_LUXEL( lightmapNum, x, lm->h - 1 );
2592 VectorAdd( bspLuxel, bspLuxel2, bspLuxel );
2593 VectorScale( bspLuxel, 0.5f, bspLuxel );
2594 VectorCopy( bspLuxel, bspLuxel2 );
2595 if( deluxemap && lightmapNum == 0 )
2597 bspDeluxel = BSP_DELUXEL( x, 0 );
2598 bspDeluxel2 = BSP_DELUXEL( x, lm->h - 1 );
2599 VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel );
2600 VectorScale( bspDeluxel, 0.5f, bspDeluxel );
2601 VectorCopy( bspDeluxel, bspDeluxel2 );
2608 /* -----------------------------------------------------------------
2609 collapse non-unique lightmaps
2610 ----------------------------------------------------------------- */
2612 if( noCollapse == qfalse && deluxemap == qfalse )
2615 Sys_FPrintf( SYS_VRB, "collapsing..." );
2617 /* set all twin refs to null */
2618 for( i = 0; i < numRawLightmaps; i++ )
2620 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2622 rawLightmaps[ i ].twins[ lightmapNum ] = NULL;
2623 rawLightmaps[ i ].twinNums[ lightmapNum ] = -1;
2624 rawLightmaps[ i ].numStyledTwins = 0;
2628 /* walk the list of raw lightmaps */
2629 for( i = 0; i < numRawLightmaps; i++ )
2632 lm = &rawLightmaps[ i ];
2634 /* walk lightmaps */
2635 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2638 if( lm->bspLuxels[ lightmapNum ] == NULL ||
2639 lm->twins[ lightmapNum ] != NULL )
2642 /* find all lightmaps that are virtually identical to this one */
2643 for( j = i + 1; j < numRawLightmaps; j++ )
2646 lm2 = &rawLightmaps[ j ];
2648 /* walk lightmaps */
2649 for( lightmapNum2 = 0; lightmapNum2 < MAX_LIGHTMAPS; lightmapNum2++ )
2652 if( lm2->bspLuxels[ lightmapNum2 ] == NULL ||
2653 lm2->twins[ lightmapNum2 ] != NULL )
2657 if( CompareBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) )
2659 /* merge and set twin */
2660 if( MergeBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) )
2662 lm2->twins[ lightmapNum2 ] = lm;
2663 lm2->twinNums[ lightmapNum2 ] = lightmapNum;
2665 numTwinLuxels += (lm->w * lm->h);
2667 /* count styled twins */
2668 if( lightmapNum > 0 )
2669 lm->numStyledTwins++;
2678 /* -----------------------------------------------------------------
2679 sort raw lightmaps by shader
2680 ----------------------------------------------------------------- */
2683 Sys_FPrintf( SYS_VRB, "sorting..." );
2685 /* allocate a new sorted list */
2686 if( sortLightmaps == NULL )
2687 sortLightmaps = safe_malloc( numRawLightmaps * sizeof( int ) );
2689 /* fill it out and sort it */
2690 for( i = 0; i < numRawLightmaps; i++ )
2691 sortLightmaps[ i ] = i;
2692 qsort( sortLightmaps, numRawLightmaps, sizeof( int ), CompareRawLightmap );
2694 /* -----------------------------------------------------------------
2695 allocate output lightmaps
2696 ----------------------------------------------------------------- */
2699 Sys_FPrintf( SYS_VRB, "allocating..." );
2701 /* kill all existing output lightmaps */
2702 if( outLightmaps != NULL )
2704 for( i = 0; i < numOutLightmaps; i++ )
2706 free( outLightmaps[ i ].lightBits );
2707 free( outLightmaps[ i ].bspLightBytes );
2709 free( outLightmaps );
2710 outLightmaps = NULL;
2713 numLightmapShaders = 0;
2714 numOutLightmaps = 0;
2715 numBSPLightmaps = 0;
2716 numExtLightmaps = 0;
2718 /* find output lightmap */
2719 for( i = 0; i < numRawLightmaps; i++ )
2721 lm = &rawLightmaps[ sortLightmaps[ i ] ];
2722 FindOutLightmaps( lm );
2725 /* set output numbers in twinned lightmaps */
2726 for( i = 0; i < numRawLightmaps; i++ )
2729 lm = &rawLightmaps[ sortLightmaps[ i ] ];
2731 /* walk lightmaps */
2732 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2735 lm2 = lm->twins[ lightmapNum ];
2738 lightmapNum2 = lm->twinNums[ lightmapNum ];
2740 /* find output lightmap from twin */
2741 lm->outLightmapNums[ lightmapNum ] = lm2->outLightmapNums[ lightmapNum2 ];
2742 lm->lightmapX[ lightmapNum ] = lm2->lightmapX[ lightmapNum2 ];
2743 lm->lightmapY[ lightmapNum ] = lm2->lightmapY[ lightmapNum2 ];
2747 /* -----------------------------------------------------------------
2748 store output lightmaps
2749 ----------------------------------------------------------------- */
2752 Sys_FPrintf( SYS_VRB, "storing..." );
2754 /* count the bsp lightmaps and allocate space */
2755 if( bspLightBytes != NULL )
2756 free( bspLightBytes );
2757 if( numBSPLightmaps == 0 || externalLightmaps )
2759 numBSPLightBytes = 0;
2760 bspLightBytes = NULL;
2764 numBSPLightBytes = (numBSPLightmaps * game->lightmapSize * game->lightmapSize * 3);
2765 bspLightBytes = safe_malloc( numBSPLightBytes );
2766 memset( bspLightBytes, 0, numBSPLightBytes );
2769 /* walk the list of output lightmaps */
2770 for( i = 0; i < numOutLightmaps; i++ )
2772 /* get output lightmap */
2773 olm = &outLightmaps[ i ];
2775 /* is this a valid bsp lightmap? */
2776 if( olm->lightmapNum >= 0 && !externalLightmaps )
2778 /* copy lighting data */
2779 lb = bspLightBytes + (olm->lightmapNum * game->lightmapSize * game->lightmapSize * 3);
2780 memcpy( lb, olm->bspLightBytes, game->lightmapSize * game->lightmapSize * 3 );
2782 /* copy direction data */
2785 lb = bspLightBytes + ((olm->lightmapNum + 1) * game->lightmapSize * game->lightmapSize * 3);
2786 memcpy( lb, olm->bspDirBytes, game->lightmapSize * game->lightmapSize * 3 );
2790 /* external lightmap? */
2791 if( olm->lightmapNum < 0 || olm->extLightmapNum >= 0 || externalLightmaps )
2793 /* make a directory for the lightmaps */
2796 /* set external lightmap number */
2797 olm->extLightmapNum = numExtLightmaps;
2799 /* write lightmap */
2800 sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps );
2801 Sys_FPrintf( SYS_VRB, "\nwriting %s", filename );
2802 WriteTGA24( filename, olm->bspLightBytes, olm->customWidth, olm->customHeight, qtrue );
2805 /* write deluxemap */
2808 sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps );
2809 Sys_FPrintf( SYS_VRB, "\nwriting %s", filename );
2810 WriteTGA24( filename, olm->bspDirBytes, olm->customWidth, olm->customHeight, qtrue );
2813 if( debugDeluxemap )
2814 olm->extLightmapNum++;
2819 if( numExtLightmaps > 0 )
2820 Sys_FPrintf( SYS_VRB, "\n" );
2822 /* delete unused external lightmaps */
2823 for( i = numExtLightmaps; i; i++ )
2825 /* determine if file exists */
2826 sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, i );
2827 if( !FileExists( filename ) )
2834 /* -----------------------------------------------------------------
2835 project the lightmaps onto the bsp surfaces
2836 ----------------------------------------------------------------- */
2839 Sys_FPrintf( SYS_VRB, "projecting..." );
2841 /* walk the list of surfaces */
2842 for( i = 0; i < numBSPDrawSurfaces; i++ )
2844 /* get the surface and info */
2845 ds = &bspDrawSurfaces[ i ];
2846 info = &surfaceInfos[ i ];
2850 /* handle surfaces with identical parent */
2851 if( info->parentSurfaceNum >= 0 )
2853 /* preserve original data and get parent */
2854 parent = &bspDrawSurfaces[ info->parentSurfaceNum ];
2855 memcpy( &dsTemp, ds, sizeof( *ds ) );
2857 /* overwrite child with parent data */
2858 memcpy( ds, parent, sizeof( *ds ) );
2860 /* restore key parts */
2861 ds->fogNum = dsTemp.fogNum;
2862 ds->firstVert = dsTemp.firstVert;
2863 ds->firstIndex = dsTemp.firstIndex;
2864 memcpy( ds->lightmapVecs, dsTemp.lightmapVecs, sizeof( dsTemp.lightmapVecs ) );
2866 /* set vertex data */
2867 dv = &bspDrawVerts[ ds->firstVert ];
2868 dvParent = &bspDrawVerts[ parent->firstVert ];
2869 for( j = 0; j < ds->numVerts; j++ )
2871 memcpy( dv[ j ].lightmap, dvParent[ j ].lightmap, sizeof( dv[ j ].lightmap ) );
2872 memcpy( dv[ j ].color, dvParent[ j ].color, sizeof( dv[ j ].color ) );
2879 /* handle vertex lit or approximated surfaces */
2880 else if( lm == NULL || lm->outLightmapNums[ 0 ] < 0 )
2882 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2884 ds->lightmapNum[ lightmapNum ] = -3;
2885 ds->lightmapStyles[ lightmapNum ] = ds->vertexStyles[ lightmapNum ];
2889 /* handle lightmapped surfaces */
2892 /* walk lightmaps */
2893 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2896 ds->lightmapStyles[ lightmapNum ] = lm->styles[ lightmapNum ];
2898 /* handle unused style */
2899 if( lm->styles[ lightmapNum ] == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 )
2901 ds->lightmapNum[ lightmapNum ] = -3;
2905 /* get output lightmap */
2906 olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ];
2908 /* set bsp lightmap number */
2909 ds->lightmapNum[ lightmapNum ] = olm->lightmapNum;
2911 /* deluxemap debugging makes the deluxemap visible */
2912 if( deluxemap && debugDeluxemap && lightmapNum == 0 )
2913 ds->lightmapNum[ lightmapNum ]++;
2915 /* calc lightmap origin in texture space */
2916 lmx = (float) lm->lightmapX[ lightmapNum ] / (float) olm->customWidth;
2917 lmy = (float) lm->lightmapY[ lightmapNum ] / (float) olm->customHeight;
2919 /* calc lightmap st coords */
2920 dv = &bspDrawVerts[ ds->firstVert ];
2921 ydv = &yDrawVerts[ ds->firstVert ];
2922 for( j = 0; j < ds->numVerts; j++ )
2924 if( lm->solid[ lightmapNum ] )
2926 dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + (0.5f / (float) olm->customWidth);
2927 dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + (0.5f / (float) olm->customWidth);
2931 dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + (ydv[ j ].lightmap[ 0 ][ 0 ] / (superSample * olm->customWidth));
2932 dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + (ydv[ j ].lightmap[ 0 ][ 1 ] / (superSample * olm->customHeight));
2938 /* store vertex colors */
2939 dv = &bspDrawVerts[ ds->firstVert ];
2940 for( j = 0; j < ds->numVerts; j++ )
2942 /* walk lightmaps */
2943 for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2945 /* handle unused style */
2946 if( ds->vertexStyles[ lightmapNum ] == LS_NONE )
2947 VectorClear( color );
2950 /* get vertex color */
2951 luxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + j );
2952 VectorCopy( luxel, color );
2954 /* set minimum light */
2955 if( lightmapNum == 0 )
2957 for( k = 0; k < 3; k++ )
2958 if( color[ k ] < minVertexLight[ k ] )
2959 color[ k ] = minVertexLight[ k ];
2963 /* store to bytes */
2964 if( !info->si->noVertexLight )
2965 ColorToBytes( color, dv[ j ].color[ lightmapNum ], info->si->vertexScale );
2969 /* surfaces with styled lightmaps and a style marker get a custom generated shader (fixme: make this work with external lightmaps) */
2970 if( olm != NULL && lm != NULL && lm->styles[ 1 ] != LS_NONE && game->load != LoadRBSPFile ) //% info->si->styleMarker > 0 )
2973 char key[ 32 ], styleStage[ 512 ], styleStages[ 4096 ], rgbGen[ 128 ], alphaGen[ 128 ];
2977 sprintf( styleStages, "\n\t// Q3Map2 custom lightstyle stage(s)\n" );
2978 dv = &bspDrawVerts[ ds->firstVert ];
2980 /* depthFunc equal? */
2981 if( info->si->styleMarker == 2 || info->si->implicitMap == IM_MASKED )
2986 /* generate stages for styled lightmaps */
2987 for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
2990 style = lm->styles[ lightmapNum ];
2991 if( style == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 )
2994 /* get output lightmap */
2995 olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ];
2998 if( lm->outLightmapNums[ lightmapNum ] == lm->outLightmapNums[ 0 ] )
2999 strcpy( lightmapName, "$lightmap" );
3001 sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum );
3003 /* get rgbgen string */
3004 if( rgbGenValues[ style ] == NULL )
3006 sprintf( key, "_style%drgbgen", style );
3007 rgbGenValues[ style ] = (char*) ValueForKey( &entities[ 0 ], key );
3008 if( rgbGenValues[ style ][ 0 ] == '\0' )
3009 rgbGenValues[ style ] = "wave noise 0.5 1 0 5.37";
3012 if( rgbGenValues[ style ][ 0 ] != '\0' )
3013 sprintf( rgbGen, "\t\trgbGen %s // style %d\n", rgbGenValues[ style ], style );
3017 /* get alphagen string */
3018 if( alphaGenValues[ style ] == NULL )
3020 sprintf( key, "_style%dalphagen", style );
3021 alphaGenValues[ style ] = (char*) ValueForKey( &entities[ 0 ], key );
3023 if( alphaGenValues[ style ][ 0 ] != '\0' )
3024 sprintf( alphaGen, "\t\talphaGen %s // style %d\n", alphaGenValues[ style ], style );
3026 alphaGen[ 0 ] = '\0';
3028 /* calculate st offset */
3029 lmx = dv[ 0 ].lightmap[ lightmapNum ][ 0 ] - dv[ 0 ].lightmap[ 0 ][ 0 ];
3030 lmy = dv[ 0 ].lightmap[ lightmapNum ][ 1 ] - dv[ 0 ].lightmap[ 0 ][ 1 ];
3032 /* create additional stage */
3033 if( lmx == 0.0f && lmy == 0.0f )
3035 sprintf( styleStage, "\t{\n"
3036 "\t\tmap %s\n" /* lightmap */
3037 "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n"
3038 "%s" /* depthFunc equal */
3041 "\t\ttcGen lightmap\n"
3044 (dfEqual ? "\t\tdepthFunc equal\n" : ""),
3050 sprintf( styleStage, "\t{\n"
3051 "\t\tmap %s\n" /* lightmap */
3052 "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n"
3053 "%s" /* depthFunc equal */
3056 "\t\ttcGen lightmap\n"
3057 "\t\ttcMod transform 1 0 0 1 %1.5f %1.5f\n" /* st offset */
3060 (dfEqual ? "\t\tdepthFunc equal\n" : ""),
3068 strcat( styleStages, styleStage );
3071 /* create custom shader */
3072 if( info->si->styleMarker == 2 )
3073 csi = CustomShader( info->si, "q3map_styleMarker2", styleStages );
3075 csi = CustomShader( info->si, "q3map_styleMarker", styleStages );
3077 /* emit remap command */
3078 //% EmitVertexRemapShader( csi->shader, info->si->shader );
3081 //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) );
3082 ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags );
3083 //% Sys_Printf( ")\n" );
3086 /* devise a custom shader for this surface (fixme: make this work with light styles) */
3087 else if( olm != NULL && lm != NULL && !externalLightmaps &&
3088 (olm->customWidth != game->lightmapSize || olm->customHeight != game->lightmapSize) )
3090 /* get output lightmap */
3091 olm = &outLightmaps[ lm->outLightmapNums[ 0 ] ];
3093 /* do some name mangling */
3094 sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum );
3096 /* create custom shader */
3097 csi = CustomShader( info->si, "$lightmap", lightmapName );
3100 //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) );
3101 ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags );
3102 //% Sys_Printf( ")\n" );
3105 /* use the normal plain-jane shader */
3107 ds->shaderNum = EmitShader( info->si->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags );
3111 Sys_FPrintf( SYS_VRB, "done.\n" );
3113 /* calc num stored */
3114 numStored = numBSPLightBytes / 3;
3115 efficiency = (numStored <= 0)
3117 : (float) numUsed / (float) numStored;
3120 Sys_Printf( "%9d luxels used\n", numUsed );
3121 Sys_Printf( "%9d luxels stored (%3.2f percent efficiency)\n", numStored, efficiency * 100.0f );
3122 Sys_Printf( "%9d solid surface lightmaps\n", numSolidLightmaps );
3123 Sys_Printf( "%9d identical surface lightmaps, using %d luxels\n", numTwins, numTwinLuxels );
3124 Sys_Printf( "%9d vertex forced surfaces\n", numSurfsVertexForced );
3125 Sys_Printf( "%9d vertex approximated surfaces\n", numSurfsVertexApproximated );
3126 Sys_Printf( "%9d BSP lightmaps\n", numBSPLightmaps );
3127 Sys_Printf( "%9d total lightmaps\n", numOutLightmaps );
3128 Sys_Printf( "%9d unique lightmap/shader combinations\n", numLightmapShaders );
3130 /* write map shader file */
3131 WriteMapShaderFile();