/* ------------------------------------------------------------------------------- This code is based on source provided under the terms of the Id Software LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. All changes and additions to the original source which have been developed by other contributors (see CONTRIBUTORS) are provided under the terms of the license the contributors choose (see LICENSE), to the extent permitted by the LICENSE_ID. If you did not receive a copy of the contributor license, please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------------- This code has been altered significantly from its original form, to support several games based on the Quake III Arena engine, in the form of "Q3Map2." ------------------------------------------------------------------------------- */ /* marker */ #define LIGHTMAPS_YDNAR_C /* dependencies */ #include "q3map2.h" /* ------------------------------------------------------------------------------- this file contains code that doe lightmap allocation and projection that runs in the -light phase. this is handled here rather than in the bsp phase for a few reasons-- surfaces are no longer necessarily convex polygons, patches may or may not be planar or have lightmaps projected directly onto control points. also, this allows lightmaps to be calculated before being allocated and stored in the bsp. lightmaps that have little high-frequency information are candidates for having their resolutions scaled down. ------------------------------------------------------------------------------- */ /* WriteTGA24() based on WriteTGA() from imagelib.c */ void WriteTGA24( char *filename, byte *data, int width, int height, qboolean flip ) { int i, c; byte *buffer, *in; FILE *file; /* allocate a buffer and set it up */ buffer = safe_malloc( width * height * 3 + 18 ); memset( buffer, 0, 18 ); buffer[ 2 ] = 2; buffer[ 12 ] = width & 255; buffer[ 13 ] = width >> 8; buffer[ 14 ] = height & 255; buffer[ 15 ] = height >> 8; buffer[ 16 ] = 24; /* swap rgb to bgr */ c = (width * height * 3) + 18; for( i = 18; i < c; i += 3 ) { buffer[ i ] = data[ i - 18 + 2 ]; /* blue */ buffer[ i + 1 ] = data[ i - 18 + 1 ]; /* green */ buffer[ i + 2 ] = data[ i - 18 + 0 ]; /* red */ } /* write it and free the buffer */ file = fopen( filename, "wb" ); if( file == NULL ) Error( "Unable to open %s for writing", filename ); /* flip vertically? */ if( flip ) { fwrite( buffer, 1, 18, file ); for( in = buffer + ((height - 1) * width * 3) + 18; in >= buffer; in -= (width * 3) ) fwrite( in, 1, (width * 3), file ); } else fwrite( buffer, 1, c, file ); /* close the file */ fclose( file ); free( buffer ); } /* ExportLightmaps() exports the lightmaps as a list of numbered tga images */ void ExportLightmaps( void ) { int i; char dirname[ MAX_QPATH ], filename[ MAX_QPATH ]; byte *lightmap; /* note it */ Sys_FPrintf( SYS_VRB, "--- ExportLightmaps ---\n"); /* do some path mangling */ strcpy( dirname, source ); StripExtension( dirname ); /* sanity check */ if( bspLightBytes == NULL ) Error( "No lightmap data" ); /* make a directory for the lightmaps */ Q_mkdir( dirname ); /* iterate through the lightmaps */ for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3) ) { /* write a tga image out */ sprintf( filename, "%s/lightmap_%03d.tga", dirname, i ); Sys_Printf( "writing %s\n", filename ); WriteTGA24( filename, lightmap, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, qfalse ); } } /* ExportLightmapsMain() exports the lightmaps as a list of numbered tga images */ int ExportLightmapsMain( int argc, char **argv ) { /* arg checking */ if( argc < 1 ) { Sys_Printf( "Usage: q3map -export [-v] \n" ); return 0; } /* do some path mangling */ strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); StripExtension( source ); DefaultExtension( source, ".bsp" ); /* load the bsp */ Sys_Printf( "Loading %s\n", source ); LoadBSPFile( source ); /* export the lightmaps */ ExportLightmaps(); /* return to sender */ return 0; } /* ImportLightmapsMain() imports the lightmaps from a list of numbered tga images */ int ImportLightmapsMain( int argc, char **argv ) { int i, x, y, len, width, height; char dirname[ MAX_QPATH ], filename[ MAX_QPATH ]; byte *lightmap, *buffer, *pixels, *in, *out; /* arg checking */ if( argc < 1 ) { Sys_Printf( "Usage: q3map -import [-v] \n" ); return 0; } /* do some path mangling */ strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); StripExtension( source ); DefaultExtension( source, ".bsp" ); /* load the bsp */ Sys_Printf( "Loading %s\n", source ); LoadBSPFile( source ); /* note it */ Sys_FPrintf( SYS_VRB, "--- ImportLightmaps ---\n"); /* do some path mangling */ strcpy( dirname, source ); StripExtension( dirname ); /* sanity check */ if( bspLightBytes == NULL ) Error( "No lightmap data" ); /* make a directory for the lightmaps */ Q_mkdir( dirname ); /* iterate through the lightmaps */ for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3) ) { /* read a tga image */ sprintf( filename, "%s/lightmap_%03d.tga", dirname, i ); Sys_Printf( "Loading %s\n", filename ); buffer = NULL; len = vfsLoadFile( filename, (void*) &buffer, -1 ); if( len < 0 ) { Sys_Printf( "WARNING: Unable to load image %s\n", filename ); continue; } /* parse file into an image */ pixels = NULL; LoadTGABuffer( buffer, &pixels, &width, &height ); free( buffer ); /* sanity check it */ if( pixels == NULL ) { Sys_Printf( "WARNING: Unable to load image %s\n", filename ); continue; } if( width != LIGHTMAP_WIDTH || height != LIGHTMAP_HEIGHT ) Sys_Printf( "WARNING: Image %s is not the right size (%d, %d) != (%d, %d)\n", filename, width, height, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT ); /* copy the pixels */ in = pixels; for( y = 1; y <= LIGHTMAP_HEIGHT; y++ ) { out = lightmap + ((LIGHTMAP_HEIGHT - y) * LIGHTMAP_WIDTH * 3); for( x = 0; x < LIGHTMAP_WIDTH; x++, in += 4, out += 3 ) VectorCopy( in, out ); } /* free the image */ free( pixels ); } /* write the bsp */ Sys_Printf( "writing %s\n", source ); WriteBSPFile( source ); /* return to sender */ return 0; } /* ------------------------------------------------------------------------------- this section deals with projecting a lightmap onto a raw drawsurface ------------------------------------------------------------------------------- */ /* FinishRawLightmap() allocates a raw lightmap's necessary buffers */ void FinishRawLightmap( rawLightmap_t *lm ) { int i, size, *sc; float is; /* set supersampling size */ lm->sw = lm->w * superSample; lm->sh = lm->h * superSample; /* manipulate origin/vecs for supersampling */ if( superSample > 1 && lm->vecs != NULL ) { /* calc inverse supersample */ is = 1.0f / superSample; /* scale the vectors and shift the origin */ #if 1 /* new code that works for arbitrary supersampling values */ VectorMA( lm->origin, -0.5, lm->vecs[ 0 ], lm->origin ); VectorMA( lm->origin, -0.5, lm->vecs[ 1 ], lm->origin ); VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); VectorMA( lm->origin, is, lm->vecs[ 0 ], lm->origin ); VectorMA( lm->origin, is, lm->vecs[ 1 ], lm->origin ); #else /* old code that only worked with a value of 2 */ VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); VectorMA( lm->origin, -is, lm->vecs[ 0 ], lm->origin ); VectorMA( lm->origin, -is, lm->vecs[ 1 ], lm->origin ); #endif } /* allocate bsp lightmap storage */ size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float ); if( lm->bspLuxels[ 0 ] == NULL ) lm->bspLuxels[ 0 ] = safe_malloc( size ); memset( lm->bspLuxels[ 0 ], 0, size ); /* allocate radiosity lightmap storage */ if( bounce ) { size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float ); if( lm->radLuxels[ 0 ] == NULL ) lm->radLuxels[ 0 ] = safe_malloc( size ); memset( lm->radLuxels[ 0 ], 0, size ); } /* allocate sampling lightmap storage */ size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); if( lm->superLuxels[ 0 ] == NULL ) lm->superLuxels[ 0 ] = safe_malloc( size ); memset( lm->superLuxels[ 0 ], 0, size ); /* allocate origin map storage */ size = lm->sw * lm->sh * SUPER_ORIGIN_SIZE * sizeof( float ); if( lm->superOrigins == NULL ) lm->superOrigins = safe_malloc( size ); memset( lm->superOrigins, 0, size ); /* allocate normal map storage */ size = lm->sw * lm->sh * SUPER_NORMAL_SIZE * sizeof( float ); if( lm->superNormals == NULL ) lm->superNormals = safe_malloc( size ); memset( lm->superNormals, 0, size ); /* allocate cluster map storage */ size = lm->sw * lm->sh * sizeof( int ); if( lm->superClusters == NULL ) lm->superClusters = safe_malloc( size ); size = lm->sw * lm->sh; sc = lm->superClusters; for( i = 0; i < size; i++ ) (*sc++) = CLUSTER_UNMAPPED; /* add to count */ numLuxels += (lm->sw * lm->sh); } /* AddPatchToRawLightmap() projects a lightmap for a patch surface since lightmap calculation for surfaces is now handled in a general way (light_ydnar.c), it is no longer necessary for patch verts to fall exactly on a lightmap sample based on AllocateLightmapForPatch() */ qboolean AddPatchToRawLightmap( int num, rawLightmap_t *lm ) { bspDrawSurface_t *ds; surfaceInfo_t *info; int x, y; bspDrawVert_t *verts, *a, *b; vec3_t delta; mesh_t src, *subdivided, *mesh; float sBasis, tBasis, s, t; float length, widthTable[ MAX_EXPANDED_AXIS ], heightTable[ MAX_EXPANDED_AXIS ]; /* patches finish a raw lightmap */ lm->finished = qtrue; /* get surface and info */ ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* make a temporary mesh from the drawsurf */ src.width = ds->patchWidth; src.height = ds->patchHeight; src.verts = &yDrawVerts[ ds->firstVert ]; subdivided = SubdivideMesh( src, 8, 999 ); /* fit it to the curve and remove colinear verts on rows/columns */ PutMeshOnCurve( *subdivided ); mesh = RemoveLinearMeshColumnsRows( subdivided ); FreeMesh( subdivided ); /* find the longest distance on each row/column */ verts = mesh->verts; memset( widthTable, 0, sizeof( widthTable ) ); memset( heightTable, 0, sizeof( heightTable ) ); for( y = 0; y < mesh->height; y++ ) { for( x = 0; x < mesh->width; x++ ) { /* get width */ if( x < (mesh->width- 1) ) { a = &verts[ (y * mesh->width) + x ]; b = &verts[ (y * mesh->width) + x + 1 ]; VectorSubtract( a->xyz, b->xyz, delta ); length = VectorLength( delta ); if( length > widthTable[ x ] ) widthTable[ x ] = length; } /* get height */ if( y < (mesh->height - 1) ) { a = &verts[ (y * mesh->width) + x ]; b = &verts[ ((y + 1) * mesh->width) + x ]; VectorSubtract( a->xyz, b->xyz, delta ); length = VectorLength( delta ); if( length > heightTable[ y ] ) heightTable[ y ] = length; } } } /* determine lightmap width */ length = 0; for( x = 0; x < (mesh->width - 1); x++ ) length += widthTable[ x ]; lm->w = ceil( length / lm->sampleSize ) + 1; if( lm->w < ds->patchWidth ) lm->w = ds->patchWidth; if( lm->w > lm->customWidth ) lm->w = lm->customWidth; sBasis = (float) (lm->w - 1) / (float) (ds->patchWidth - 1); /* determine lightmap height */ length = 0; for( y = 0; y < (mesh->height - 1); y++ ) length += heightTable[ y ]; lm->h = ceil( length / lm->sampleSize ) + 1; if( lm->h < ds->patchHeight ) lm->h = ds->patchHeight; if( lm->h > lm->customHeight ) lm->h = lm->customHeight; tBasis = (float) (lm->h - 1) / (float) (ds->patchHeight - 1); /* free the temporary mesh */ FreeMesh( mesh ); /* set the lightmap texture coordinates in yDrawVerts */ verts = &yDrawVerts[ ds->firstVert ]; for( y = 0; y < ds->patchHeight; y++ ) { t = (tBasis * y) + 0.5; for( x = 0; x < ds->patchWidth; x++ ) { s = (sBasis * x) + 0.5; verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 0 ] = s * superSample; verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 1 ] = t * superSample; } } /* debug code: remove me!! */ //% if( lm->w > (ds->lightmapWidth & 0xFF) || lm->h > (ds->lightmapHeight & 0xFF) ) //% Sys_Printf( "Patch lightmap: (%3d %3d) > (%3d, %3d)\n", lm->w, lm->h, ds->lightmapWidth & 0xFF, ds->lightmapHeight & 0xFF ); //% ds->lightmapWidth = lm->w | (ds->lightmapWidth & 0xFFFF0000); //% ds->lightmapHeight = lm->h | (ds->lightmapHeight & 0xFFFF0000); /* add to counts */ numPatchesLightmapped++; /* return */ return qtrue; } /* AddSurfaceToRawLightmap() projects a lightmap for a surface based on AllocateLightmapForSurface() */ qboolean AddSurfaceToRawLightmap( int num, rawLightmap_t *lm ) { bspDrawSurface_t *ds, *ds2; surfaceInfo_t *info, *info2; int num2, n, i, axisNum; float s, t, d, len, sampleSize; vec3_t mins, maxs, size, exactSize, delta, normalized, vecs[ 2 ]; vec4_t plane; bspDrawVert_t *verts; /* get surface and info */ ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* add the surface to the raw lightmap */ lightSurfaces[ numLightSurfaces++ ] = num; lm->numLightSurfaces++; /* does this raw lightmap already have any surfaces? */ if( lm->numLightSurfaces > 1 ) { /* surface and raw lightmap must have the same lightmap projection axis */ if( VectorCompare( info->axis, lm->axis ) == qfalse ) return qfalse; /* they must have the same samplesize, ent num, lightmap size and gamma */ if( info->sampleSize != lm->sampleSize || info->entityNum != lm->entityNum || info->si->lmCustomWidth != lm->customWidth || info->si->lmCustomHeight != lm->customHeight || info->si->lmGamma != lm->gamma ) return qfalse; /* surface bounds must intersect with raw lightmap bounds */ for( i = 0; i < 3; i++ ) { if( info->mins[ i ] > lm->maxs[ i ] ) return qfalse; if( info->maxs[ i ] < lm->mins[ i ] ) return qfalse; } /* plane check (fixme: allow merging of nonplanars) */ if( info->plane == NULL ) return qfalse; if( lm->plane == NULL ) return qfalse; for( i = 0; i < 4; i++ ) if( fabs( info->plane[ i ] - lm->plane[ i ] ) > EQUAL_EPSILON ) return qfalse; } /* add surface to lightmap bounds */ AddPointToBounds( info->mins, lm->mins, lm->maxs ); AddPointToBounds( info->maxs, lm->mins, lm->maxs ); /* check to see if this is a non-planar patch */ if( ds->surfaceType == MST_PATCH && lm->plane == NULL ) return AddPatchToRawLightmap( num, lm ); /* start with initially requested sample size */ sampleSize = lm->sampleSize; /* round to the lightmap resolution */ for( i = 0; i < 3; i++ ) { exactSize[ i ] = lm->maxs[ i ] - lm->mins[ i ]; mins[ i ] = sampleSize * floor( lm->mins[ i ] / sampleSize ); maxs[ i ] = sampleSize * ceil( lm->maxs[ i ] / sampleSize ); size[ i ] = (maxs[ i ] - mins[ i ]) / sampleSize + 1.0f; /* hack (god this sucks) */ if( size[ i ] > lm->customWidth || size[ i ] > lm->customHeight ) { i = -1; sampleSize += 1.0f; } } /* fixme: copy rounded mins/maxes to lightmap record? */ /* clear out lightmap vectors */ memset( vecs, 0, sizeof( vecs ) ); /* classify the plane (x y or z major) (ydnar: biased to z axis projection) */ if( lm->axis[ 2 ] >= lm->axis[ 0 ] && lm->axis[ 2 ] >= lm->axis[ 1 ] ) { axisNum = 2; lm->w = size[ 0 ]; lm->h = size[ 1 ]; vecs[ 0 ][ 0 ] = 1.0f / sampleSize; vecs[ 1 ][ 1 ] = 1.0f / sampleSize; } else if( lm->axis[ 0 ] >= lm->axis[ 1 ] && lm->axis[ 0 ] >= lm->axis[ 2 ] ) { axisNum = 0; lm->w = size[ 1 ]; lm->h = size[ 2 ]; vecs[ 0 ][ 1 ] = 1.0f / sampleSize; vecs[ 1 ][ 2 ] = 1.0f / sampleSize; } else { axisNum = 1; lm->w = size[ 0 ]; lm->h = size[ 2 ]; vecs[ 0 ][ 0 ] = 1.0f / sampleSize; vecs[ 1 ][ 2 ] = 1.0f / sampleSize; } /* check for bogus axis */ if( lm->axis[ axisNum ] == 0 ) { Sys_Printf( "WARNING: ProjectSurfaceLightmap: Chose a 0 valued axis" ); lm->w = lm->h = 0; return qfalse; } /* store the axis number + 1 in the lightmap */ lm->axisNum = axisNum; /* clamp to lightmap texture resolution (this is what the ugly hack above replaces) */ #if 0 if( lm->w > lm->customWidth ) { VectorScale( vecs[ 0 ], ((float) lm->customWidth / lm->w), vecs[ 0 ] ); lm->w = lm->customWidth; } if( lm->h > lm->customHeight ) { VectorScale( vecs[ 1 ], ((float) lm->customHeight / lm->h), vecs[ 1 ] ); lm->h = lm->customHeight; } #endif /* walk the list of surfaces on this raw lightmap */ for( n = 0; n < lm->numLightSurfaces; n++ ) { /* get surface */ num2 = lightSurfaces[ lm->firstLightSurface + n ]; ds2 = &bspDrawSurfaces[ num2 ]; info2 = &surfaceInfos[ num2 ]; verts = &yDrawVerts[ ds2->firstVert ]; /* set the lightmap texture coordinates in yDrawVerts in [0,superSample*lm->customWidth] space */ for( i = 0; i < ds2->numVerts; i++ ) { VectorSubtract( verts[ i ].xyz, lm->mins, delta ); s = DotProduct( delta, vecs[ 0 ] ) + 0.5f; t = DotProduct( delta, vecs[ 1 ] ) + 0.5f; verts[ i ].lightmap[ 0 ][ 0 ] = s * superSample; verts[ i ].lightmap[ 0 ][ 1 ] = t * superSample; if( s > (float) lm->w || t > (float) lm->h ) { Sys_FPrintf( SYS_VRB, "WARNING: Lightmap texture coords out of range: S %1.4f > %3d || T %1.4f > %3d\n", s, lm->w, t, lm->h ); } } } /* calculate lightmap origin */ if( VectorLength( ds->lightmapVecs[ 2 ] ) ) VectorCopy( ds->lightmapVecs[ 2 ], plane ); else VectorCopy( lm->axis, plane ); plane[ 3 ] = DotProduct( verts[ 0 ].xyz, plane ); d = DotProduct( lm->mins, plane ) - plane[ 3 ]; d /= plane[ axisNum ]; VectorCopy( lm->mins, lm->origin ); lm->origin[ axisNum ] -= d; /* legacy support */ VectorCopy( lm->origin, ds->lightmapOrigin ); /* for planar surfaces, create lightmap vectors for st->xyz conversion */ if( VectorLength( ds->lightmapVecs[ 2 ] ) || 1 ) { /* allocate space for the vectors */ lm->vecs = safe_malloc( 3 * sizeof( vec3_t ) ); memset( lm->vecs, 0, 3 * sizeof( vec3_t ) ); VectorCopy( ds->lightmapVecs[ 2 ], lm->vecs[ 2 ] ); /* project stepped lightmap blocks and subtract to get planevecs */ for( i = 0; i < 2; i++ ) { len = VectorNormalize( vecs[ i ], normalized ); VectorScale( normalized, (1.0 / len), lm->vecs[ i ] ); d = DotProduct( lm->vecs[ i ], plane ); d /= plane[ axisNum ]; lm->vecs[ i ][ axisNum ] -= d; } } else { /* lightmap vectors are useless on a non-planar surface */ lm->vecs = NULL; } /* add to counts */ if( ds->surfaceType == MST_PATCH ) { numPatchesLightmapped++; if( lm->plane != NULL ) numPlanarPatchesLightmapped++; } else { if( lm->plane != NULL ) numPlanarsLightmapped++; else numNonPlanarsLightmapped++; } /* return */ return qtrue; } /* CompareSurfaceInfo() compare function for qsort() */ static int CompareSurfaceInfo( const void *a, const void *b ) { surfaceInfo_t *aInfo, *bInfo; int i; /* get surface info */ aInfo = &surfaceInfos[ *((int*) a) ]; bInfo = &surfaceInfos[ *((int*) b) ]; /* model first */ if( aInfo->model < bInfo->model ) return 1; else if( aInfo->model > bInfo->model ) return -1; /* then lightmap status */ if( aInfo->hasLightmap < bInfo->hasLightmap ) return 1; else if( aInfo->hasLightmap > bInfo->hasLightmap ) return -1; /* then lightmap sample size */ if( aInfo->sampleSize < bInfo->sampleSize ) return 1; else if( aInfo->sampleSize > bInfo->sampleSize ) return -1; /* then lightmap axis */ for( i = 0; i < 3; i++ ) { if( aInfo->axis[ i ] < bInfo->axis[ i ] ) return 1; else if( aInfo->axis[ i ] > bInfo->axis[ i ] ) return -1; } /* then plane */ if( aInfo->plane == NULL && bInfo->plane != NULL ) return 1; else if( aInfo->plane != NULL && bInfo->plane == NULL ) return -1; else if( aInfo->plane != NULL && bInfo->plane != NULL ) { for( i = 0; i < 4; i++ ) { if( aInfo->plane[ i ] < bInfo->plane[ i ] ) return 1; else if( aInfo->plane[ i ] > bInfo->plane[ i ] ) return -1; } } /* then position in world */ for( i = 0; i < 3; i++ ) { if( aInfo->mins[ i ] < bInfo->mins[ i ] ) return 1; else if( aInfo->mins[ i ] > bInfo->mins[ i ] ) return -1; } /* these are functionally identical (this should almost never happen) */ return 0; } /* SetupSurfaceLightmaps() allocates lightmaps for every surface in the bsp that needs one this depends on yDrawVerts being allocated */ shaderInfo_t *GetSurfaceExtraShaderInfo( int num ); int GetSurfaceExtraEntityNum( int num ); int GetSurfaceExtraSampleSize( int num ); void GetSurfaceExtraLightmapAxis( int num, vec3_t lightmapAxis ); void SetupSurfaceLightmaps( void ) { int i, j, k, num, num2; bspModel_t *model; bspDrawSurface_t *ds, *ds2; surfaceInfo_t *info, *info2; rawLightmap_t *lm; qboolean added; /* note it */ Sys_FPrintf( SYS_VRB, "--- SetupSurfaceLightmaps ---\n"); /* determine supersample amount */ if( superSample < 1 ) superSample = 1; else if( superSample > 8 ) { Sys_Printf( "WARNING: Insane supersampling amount (%d) detected.\n", superSample ); superSample = 8; } /* allocate a list for per-surface info */ surfaceInfos = safe_malloc( numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); memset( surfaceInfos, 0, numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); /* allocate a list of surface indexes to be sorted */ sortSurfaces = safe_malloc( numBSPDrawSurfaces * sizeof( int ) ); memset( sortSurfaces, 0, numBSPDrawSurfaces * sizeof( int ) ); /* walk each model in the bsp */ for( i = 0; i < numBSPModels; i++ ) { /* get model */ model = &bspModels[ i ]; /* walk the list of surfaces in this model and fill out the info structs */ for( j = 0; j < model->numSurfaces; j++ ) { /* make surface index */ num = model->firstSurface + j; /* copy index to sort list */ sortSurfaces[ num ] = num; /* get surface and info */ ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* basic setup */ info->model = model; info->lm = NULL; info->plane = NULL; /* get extra data */ info->si = GetSurfaceExtraShaderInfo( num ); if( info->si == NULL ) info->si = ShaderInfoForShader( bspShaders[ ds->shaderNum ].shader ); info->entityNum = GetSurfaceExtraEntityNum( num ); info->sampleSize = GetSurfaceExtraSampleSize( num ); GetSurfaceExtraLightmapAxis( num, info->axis ); /* determine surface bounds */ ClearBounds( info->mins, info->maxs ); for( k = 0; k < ds->numVerts; k++ ) AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, info->mins, info->maxs ); /* determine if surface is planar */ if( VectorLength( ds->lightmapVecs[ 2 ] ) > 0 ) { /* make a plane */ info->plane = safe_malloc( 4 * sizeof( float ) ); VectorCopy( ds->lightmapVecs[ 2 ], info->plane ); info->plane[ 3 ] = DotProduct( yDrawVerts[ ds->firstVert ].xyz, info->plane ); } /* determine if surface requires a lightmap */ if( ds->surfaceType == MST_TRIANGLE_SOUP || VectorLength( info->axis ) <= 0 || (info->si->compileFlags & C_VERTEXLIT) ) numSurfsVertexLit++; else { numSurfsLightmapped++; info->hasLightmap = qtrue; } } } /* sort the surfaces info list */ qsort( sortSurfaces, numBSPDrawSurfaces, sizeof( int ), CompareSurfaceInfo ); /* allocate a list of surfaces that would go into raw lightmaps */ numLightSurfaces = 0; lightSurfaces = safe_malloc( numSurfsLightmapped * sizeof( int ) ); memset( lightSurfaces, 0, numSurfsLightmapped * sizeof( int ) ); /* allocate a list of raw lightmaps */ numRawLightmaps = 0; rawLightmaps = safe_malloc( numSurfsLightmapped * sizeof( *rawLightmaps ) ); memset( rawLightmaps, 0, numSurfsLightmapped * sizeof( *rawLightmaps ) ); /* walk the list of sorted surfaces */ for( i = 0; i < numBSPDrawSurfaces; i++ ) { /* get info and attempt early out */ num = sortSurfaces[ i ]; ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; if( info->hasLightmap == qfalse || info->lm != NULL ) continue; /* allocate a new raw lightmap */ lm = &rawLightmaps[ numRawLightmaps ]; numRawLightmaps++; /* set it up */ lm->firstLightSurface = numLightSurfaces; lm->numLightSurfaces = 0; lm->sampleSize = info->sampleSize; lm->entityNum = info->entityNum; lm->gamma = info->si->lmGamma; VectorCopy( info->axis, lm->axis ); lm->plane = info->plane; VectorCopy( info->mins, lm->mins ); VectorCopy( info->maxs, lm->maxs ); lm->customWidth = info->si->lmCustomWidth; lm->customHeight = info->si->lmCustomHeight; /* add the surface to the raw lightmap */ AddSurfaceToRawLightmap( num, lm ); info->lm = lm; /* do an exhaustive merge */ added = qtrue; while( added ) { /* walk the list of surfaces again */ added = qfalse; for( j = i + 1; j < numBSPDrawSurfaces && lm->finished == qfalse; j++ ) { /* get info and attempt early out */ num2 = sortSurfaces[ j ]; ds2 = &bspDrawSurfaces[ num2 ]; info2 = &surfaceInfos[ num2 ]; if( info2->hasLightmap == qfalse || info2->lm != NULL ) continue; /* add the surface to the raw lightmap */ if( AddSurfaceToRawLightmap( num2, lm ) ) { info2->lm = lm; added = qtrue; } else { /* back up one */ lm->numLightSurfaces--; numLightSurfaces--; } } } /* finish the lightmap and allocate the various buffers */ FinishRawLightmap( lm ); } /* allocate vertex luxel storage */ for( k = 0; k < MAX_LIGHTMAPS; k++ ) { vertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); memset( vertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); radVertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); memset( radVertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); } /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d surfaces\n", numBSPDrawSurfaces ); Sys_FPrintf( SYS_VRB, "%9d raw lightmaps\n", numRawLightmaps ); Sys_FPrintf( SYS_VRB, "%9d surfaces vertex lit\n", numSurfsVertexLit ); Sys_FPrintf( SYS_VRB, "%9d surfaces lightmapped\n", numSurfsLightmapped ); Sys_FPrintf( SYS_VRB, "%9d planar surfaces lightmapped\n", numPlanarsLightmapped ); Sys_FPrintf( SYS_VRB, "%9d non-planar surfaces lightmapped\n", numNonPlanarsLightmapped ); Sys_FPrintf( SYS_VRB, "%9d patches lightmapped\n", numPatchesLightmapped ); Sys_FPrintf( SYS_VRB, "%9d planar patches lightmapped\n", numPlanarPatchesLightmapped ); } /* CompareBSPLuxels() compares two surface lightmaps' bsp luxels, ignoring occluded luxels */ #define LUXEL_TOLERANCE 0.0025 #define LUXEL_COLOR_FRAC 0.001302083 /* 1 / 3 / 256 */ qboolean CompareBSPLuxels( rawLightmap_t *a, rawLightmap_t *b ) { rawLightmap_t *lm; int x, y; double delta, total; float *aLuxel, *bLuxel; /* compare */ if( a->w != b->w || a->h != b->h || a->customWidth != b->customWidth || a->customHeight != b->customHeight || a->gamma != b->gamma ) return qfalse; /* compare luxels */ delta = 0.0; total = 0.0; for( y = 0; y < a->h; y++ ) { for( x = 0; x < a->w; x++ ) { /* increment total */ total += 1.0; /* get luxels */ lm = a; aLuxel = BSP_LUXEL( 0, x, y ); lm = b; bLuxel = BSP_LUXEL( 0, x, y ); /* ignore unused luxels */ if( aLuxel[ 0 ] < 0 || bLuxel[ 0 ] < 0 ) continue; /* compare (fixme: take into account perceptual differences) */ delta += (fabs( aLuxel[ 0 ] - bLuxel[ 0 ] ) * LUXEL_COLOR_FRAC); delta += (fabs( aLuxel[ 1 ] - bLuxel[ 1 ] ) * LUXEL_COLOR_FRAC); delta += (fabs( aLuxel[ 2 ] - bLuxel[ 2 ] ) * LUXEL_COLOR_FRAC); /* is the change too high? */ if( total > 0.0 && ((delta / total) > LUXEL_TOLERANCE) ) return qfalse; } } /* made it this far, they must be identical (or close enough) */ return qtrue; } /* MergeBSPLuxels() merges two surface lightmaps' bsp luxels, overwriting occluded luxels */ void MergeBSPLuxels( rawLightmap_t *a, rawLightmap_t *b ) { rawLightmap_t *lm; int x, y; float luxel[ 3 ], *aLuxel, *bLuxel; /* compare */ if( a->w != b->w || a->h != b->h || a->customWidth != b->customWidth || a->customHeight != b->customHeight || a->gamma != b->gamma ) return; /* merge luxels */ for( y = 0; y < a->h; y++ ) { for( x = 0; x < a->w; x++ ) { /* get luxels */ lm = a; aLuxel = BSP_LUXEL( 0, x, y ); lm = b; bLuxel = BSP_LUXEL( 0, x, y ); /* handle occlusion mismatch */ if( aLuxel[ 0 ] < 0.0f ) VectorCopy( bLuxel, aLuxel ); else if( bLuxel[ 0 ] < 0.0f ) VectorCopy( aLuxel, bLuxel ); else { /* average */ VectorAdd( aLuxel, bLuxel, luxel ); VectorScale( luxel, 0.5f, luxel ); VectorCopy( luxel, aLuxel ); VectorCopy( luxel, bLuxel ); } } } } /* ApproximateLuxel() determines if a single luxel is can be approximated with the interpolated vertex rgba */ qboolean ApproximateLuxel( rawLightmap_t *lm, bspDrawVert_t *dv ) #ifdef BILERP_APPROX { int i, x, y, c; float s, t, sFrac, tFrac, *luxel, *luxel2; vec3_t color, color2; byte colorBytes[ 4 ]; /* get lightmap st */ s = dv->lightmap[ 0 ] / superSample; t = dv->lightmap[ 1 ] / superSample; /* find luxel xy coords */ x = s; y = t; if( x < 0 ) x = 0; else if( x >= lm->w ) x = lm->w - 1; if( y < 0 ) y = 0; else if( y >= lm->h ) y = lm->h - 1; /* find bilerp fraction */ s -= 0.5f; t -= 0.5f; sFrac = s - floor( s ); tFrac = t - floor( t ); /* bilerp 4 bsp lightmap samples */ luxel = BSP_LUXEL( x, y ); luxel2 = BSP_LUXEL( x + 1, y ); for( i = 0; i < 3; i++ ) color[ i ] = ((1.0f - sFrac) * luxel[ i ]) + (sFrac * luxel2[ i ]); luxel = BSP_LUXEL( x, y + 1 ); luxel2 = BSP_LUXEL( x + 1, y + 1 ); for( i = 0; i < 3; i++ ) color2[ i ] = ((1.0f - sFrac) * luxel[ i ]) + (sFrac * luxel2[ i ]); for( i = 0; i < 3; i++ ) color[ i ] = ((1.0f - tFrac) * color[ i ]) + (tFrac * color2[ i ]); /* reduce to bytes */ ColorToBytes( color, colorBytes, 1.0f ); /* compare */ for( i = 0; i < 3; i++ ) { c = (int) colorBytes[ i ] - (int) dv->color[ i ]; if( c < 0 ) c *= -1; if( c > approximateTolerance ) return qfalse; } /* must be the same */ return qtrue; } #else /* single-sample version */ { int i, x, y, c; float *luxel; byte colorBytes[ 4 ]; /* find luxel xy coords */ x = dv->lightmap[ 0 ][ 0 ] / superSample; y = dv->lightmap[ 0 ][ 1 ] / superSample; if( x < 0 ) x = 0; else if( x >= lm->w ) x = lm->w - 1; if( y < 0 ) y = 0; else if( y >= lm->h ) y = lm->h - 1; /* get luxel */ luxel = BSP_LUXEL( 0, x, y ); /* ignore occluded luxels */ if( luxel[ 0 ] < 0.0f || luxel[ 1 ] < 0.0f || luxel[ 2 ] < 0.0f ) return qtrue; /* reduce to bytes */ ColorToBytes( luxel, colorBytes, 1.0f ); /* compare */ for( i = 0; i < 3; i++ ) { c = (int) colorBytes[ i ] - (int) dv->color[ i ]; if( c < 0 ) c *= -1; if( c > approximateTolerance ) return qfalse; } /* must be the same */ return qtrue; } #endif /* ApproximateTriangle() determines if a single triangle can be approximated with vertex rgba */ qboolean ApproximateTriangle_r( rawLightmap_t *lm, bspDrawVert_t *dv[ 3 ] ) { bspDrawVert_t mid, *dv2[ 3 ]; int max; /* approximate the vertexes */ if( ApproximateLuxel( lm, dv[ 0 ] ) == qfalse ) return qfalse; if( ApproximateLuxel( lm, dv[ 1 ] ) == qfalse ) return qfalse; if( ApproximateLuxel( lm, dv[ 2 ] ) == qfalse ) return qfalse; /* subdivide calc */ { int i; float dx, dy, dist, maxDist; /* find the longest edge and split it */ max = -1; maxDist = 0; for( i = 0; i < 3; i++ ) { dx = dv[ i ]->lightmap[ 0 ] - dv[ (i + 1) % 3 ]->lightmap[ 0 ]; dy = dv[ i ]->lightmap[ 1 ] - dv[ (i + 1) % 3 ]->lightmap[ 1 ]; dist = sqrt( (dx * dx) + (dy * dy) ); if( dist > maxDist ) { maxDist = dist; max = i; } } /* try to early out */ if( i < 0 || maxDist < subdivideThreshold ) return qtrue; } /* split the longest edge and map it */ LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid ); if( ApproximateLuxel( lm, &mid ) == qfalse ) return qfalse; /* recurse to first triangle */ VectorCopy( dv, dv2 ); dv2[ max ] = ∣ if( ApproximateTriangle_r( lm, dv2 ) == qfalse ) return qfalse; /* recurse to second triangle */ VectorCopy( dv, dv2 ); dv2[ (max + 1) % 3 ] = ∣ return ApproximateTriangle_r( lm, dv2 ); } /* ApproximateLightmap() determines if a raw lightmap can be approximated sufficiently with vertex colors */ qboolean ApproximateLightmap( rawLightmap_t *lm ) { int n, num, i, x, y, a, b, c; bspDrawSurface_t *ds; surfaceInfo_t *info; mesh_t src, *subdivided, *mesh; bspDrawVert_t *verts, *dv[ 3 ]; qboolean approximated; /* approximating? */ if( approximateTolerance <= 0 ) return qfalse; /* assume reduced until shadow detail is found */ approximated = qtrue; /* walk the list of surfaces on this raw lightmap */ for( n = 0; n < lm->numLightSurfaces; n++ ) { /* get surface */ num = lightSurfaces[ lm->firstLightSurface + n ]; ds = &bspDrawSurfaces[ num ]; info = &surfaceInfos[ num ]; /* bail if lightmap doesn't match up */ if( info->lm != lm ) continue; /* assume reduced initially */ info->approximated = qtrue; /* assume that surfaces whose bounding boxes is smaller than 2x samplesize will be forced to vertex */ if( (info->maxs[ 0 ] - info->mins[ 0 ]) <= (2.0f * info->sampleSize) && (info->maxs[ 1 ] - info->mins[ 1 ]) <= (2.0f * info->sampleSize) && (info->maxs[ 2 ] - info->mins[ 2 ]) <= (2.0f * info->sampleSize) ) { numSurfsVertexForced++; continue; } /* handle the triangles */ switch( ds->surfaceType ) { case MST_PLANAR: /* get verts */ verts = yDrawVerts + ds->firstVert; /* map the triangles */ for( i = 0; i < ds->numIndexes && info->approximated; i += 3 ) { a = bspDrawIndexes[ ds->firstIndex + i ]; b = bspDrawIndexes[ ds->firstIndex + i + 1 ]; c = bspDrawIndexes[ ds->firstIndex + i + 2 ]; dv[ 0 ] = &verts[ a ]; dv[ 1 ] = &verts[ b ]; dv[ 2 ] = &verts[ c ]; info->approximated = ApproximateTriangle_r( lm, dv ); } break; case MST_PATCH: /* make a mesh from the drawsurf */ src.width = ds->patchWidth; src.height = ds->patchHeight; src.verts = &yDrawVerts[ ds->firstVert ]; subdivided = SubdivideMesh( src, 8, 999 ); /* fit it to the curve and remove colinear verts on rows/columns */ PutMeshOnCurve( *subdivided ); mesh = RemoveLinearMeshColumnsRows( subdivided ); FreeMesh( subdivided ); /* get verts */ verts = mesh->verts; /* map the mesh quads */ for( y = 0; y < (mesh->height - 1) && info->approximated; y++ ) { for( x = 0; x < (mesh->width - 1) && info->approximated; x++ ) { /* get drawverts and map first triangle */ a = (y * mesh->width) + x; b = a + mesh->width; c = a + mesh->width + 1; dv[ 0 ] = &verts[ a ]; dv[ 1 ] = &verts[ b ]; dv[ 2 ] = &verts[ c ]; info->approximated = ApproximateTriangle_r( lm, dv ); /* get drawverts and map second triangle */ a = (y * mesh->width) + x; b = a + mesh->width + 1; c = a + 1; dv[ 0 ] = &verts[ a ]; dv[ 1 ] = &verts[ b ]; dv[ 2 ] = &verts[ c ]; if( info->approximated ) info->approximated = ApproximateTriangle_r( lm, dv ); } } /* free the mesh */ FreeMesh( mesh ); break; default: break; } /* reduced? */ if( info->approximated == qfalse ) approximated = qfalse; else numSurfsVertexApproximated++; } /* return */ return approximated; } /* FindOutLightmap() for a given surface lightmap, find an output lightmap page and position for it */ void FindOutLightmap( rawLightmap_t *lm ) { outLightmap_t *olm; float *luxel; byte *pixel; int i, xMax, yMax, x, y, sx, sy, ox, oy, offset; qboolean ok; /* default */ lm->outLightmapNum = -3; /* don't store twinned lightmaps */ if( lm->twin != NULL ) return; /* can this lightmap be approximated with vertex color? */ if( ApproximateLightmap( lm ) ) return; /* do some setup */ x = 0; y = 0; /* walk the list of lightmap pages */ ok = qfalse; for( i = 0; i < numOutLightmaps; i++ ) { /* get the output lightmap */ olm = &outLightmaps[ i ]; /* simple early out test */ if( olm->freeLuxels < lm->used ) continue; /* don't store non-custom raw lightmaps on custom bsp lightmaps */ if( olm->customWidth != lm->customWidth || olm->customHeight != lm->customHeight ) continue; /* set maxs */ xMax = (olm->customWidth - lm->w) + 1; yMax = (olm->customHeight - lm->h) + 1; /* walk the origin around the lightmap */ for( y = 0; y < yMax; y++ ) { for( x = 0; x < xMax; x++ ) { /* find a fine tract of lauhnd */ ok = qtrue; for( sy = 0; sy < lm->h && ok; sy++ ) { for( sx = 0; sx < lm->w && ok; sx++ ) { /* get luxel */ luxel = BSP_LUXEL( 0, sx, sy ); if( luxel[ 3 ] < 0.0f ) continue; /* get bsp lightmap coords and test */ ox = x + sx; oy = y + sy; offset = (oy * olm->customWidth) + ox; //% if( olm->lightBits[ oy ][ ox >> 3 ] & (1 << (ox & 7)) ) if( olm->lightBits[ offset >> 3 ] & (1 << (offset & 7)) ) ok = qfalse; } } if( ok ) break; } if( ok ) break; } if( ok ) break; /* reset x and y */ x = 0; y = 0; } /* no match? */ if( i >= numOutLightmaps ) { /* allocate a new output lightmap */ numOutLightmaps++; olm = safe_malloc( numOutLightmaps * sizeof( outLightmap_t ) ); if( outLightmaps != NULL && numOutLightmaps > 1 ) { memcpy( olm, outLightmaps, (numOutLightmaps - 1) * sizeof( outLightmap_t ) ); free( outLightmaps ); } outLightmaps = olm; i = numOutLightmaps - 1; olm = &outLightmaps[ i ]; /* is this a "normal" bsp-stored lightmap? */ if( lm->customWidth == LIGHTMAP_WIDTH && lm->customHeight == LIGHTMAP_HEIGHT ) { olm->lightmapNum = numBSPLightmaps; numBSPLightmaps++; } else olm->lightmapNum = -3; /* set it up */ olm->numLightmaps = 0; olm->customWidth = lm->customWidth; olm->customHeight = lm->customHeight; olm->freeLuxels = olm->customWidth * olm->customHeight; /* allocate buffers */ olm->lightBits = safe_malloc( (olm->customWidth * olm->customHeight / 8) + 8 ); memset( olm->lightBits, 0, (olm->customWidth * olm->customHeight / 8) + 8 ); olm->bspLightBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 ); memset( olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3 ); } /* add the surface lightmap to the bsp lightmap */ lm->outLightmapNum = i; lm->lightmapX = x; lm->lightmapY = y; olm->numLightmaps++; /* mark the bits used */ for( y = 0; y < lm->h; y++ ) { for( x = 0; x < lm->w; x++ ) { /* get luxel */ luxel = BSP_LUXEL( 0, x, y ); if( luxel[ 0 ] < 0.0f ) continue; /* get bsp lightmap coords */ ox = x + lm->lightmapX; oy = y + lm->lightmapY; offset = (oy * olm->customWidth) + ox; /* flag pixel as used */ //% olm->lightBits[ oy ][ ox >> 3 ] |= (1 << (ox & 7)); olm->lightBits[ offset >> 3 ] |= (1 << (offset & 7)); olm->freeLuxels--; /* store color */ pixel = olm->bspLightBytes + (((oy * olm->customWidth) + ox) * 3); ColorToBytes( luxel, pixel, lm->gamma ); } } } /* CompareRawLightmap() compare function for qsort() */ static int CompareRawLightmap( const void *a, const void *b ) { rawLightmap_t *alm, *olm; surfaceInfo_t *aInfo, *bInfo; int i, min, diff; /* get lightmaps */ alm = &rawLightmaps[ *((int*) a) ]; olm = &rawLightmaps[ *((int*) b) ]; /* get min number of surfaces */ min = (alm->numLightSurfaces < olm->numLightSurfaces ? alm->numLightSurfaces : olm->numLightSurfaces); /* iterate */ for( i = 0; i < min; i++ ) { /* get surface info */ aInfo = &surfaceInfos[ lightSurfaces[ alm->firstLightSurface + i ] ]; bInfo = &surfaceInfos[ lightSurfaces[ olm->firstLightSurface + i ] ]; /* compare shader names */ diff = strcmp( aInfo->si->shader, bInfo->si->shader ); if( diff != 0 ) return diff; } /* must be equivalent */ return 0; } /* StoreSurfaceLightmaps() stores the surface lightmaps into the bsp as byte rgb triplets */ extern int EmitShader( const char *shader ); extern char mapName[ MAX_QPATH ]; extern char mapShaderFile[ 1024 ]; void StoreSurfaceLightmaps( void ) { bspDrawSurface_t *ds; surfaceInfo_t *info; rawLightmap_t *lm, *lm2; outLightmap_t *olm; bspDrawVert_t *dv, *ydv; int i, j, x, y, lx, ly, sx, sy, *cluster, mappedSamples; float *luxel, *bspLuxel, *radLuxel, samples, occludedSamples; float sample[ 4 ], occludedSample[ 4 ]; byte *lb; int numUsed, numTwins, numTwinLuxels, numStored; float lmx, lmy, efficiency; /* note it */ Sys_Printf( "--- StoreSurfaceLightmaps ---\n"); /* ----------------------------------------------------------------- average the sampled luxels into the bsp luxels ----------------------------------------------------------------- */ /* note it */ Sys_FPrintf( SYS_VRB, "Subsampling..." ); /* walk the list of surfaces */ numUsed = 0; for( i = 0; i < numRawLightmaps; i++ ) { /* get the surface and lightmap */ lm = &rawLightmaps[ i ]; /* average supersampled luxels */ for( y = 0; y < lm->h; y++ ) { for( x = 0; x < lm->w; x++ ) { /* subsample */ samples = 0.0f; occludedSamples = 0.0f; mappedSamples = 0; VectorClear( sample ); VectorClear( occludedSample ); for( ly = 0; ly < superSample; ly++ ) { for( lx = 0; lx < superSample; lx++ ) { /* sample luxel */ sx = x * superSample + lx; sy = y * superSample + ly; luxel = SUPER_LUXEL( 0, sx, sy ); cluster = SUPER_CLUSTER( sx, sy ); /* keep track of used/occluded samples */ if( *cluster != CLUSTER_UNMAPPED ) mappedSamples++; /* handle lightmap border? */ if( lightmapBorder && (sx == 0 || sy == 0) && luxel[ 3 ] > 0.0f ) { VectorSet( sample, 255.0f, 0.0f, 0.0f ); samples += 1.0f; } /* handle debug */ else if( debug && *cluster < 0 ) { if( *cluster == CLUSTER_UNMAPPED ) VectorSet( luxel, 255, 204, 0 ); else if( *cluster == CLUSTER_OCCLUDED ) VectorSet( luxel, 255, 0, 255 ); else if( *cluster == CLUSTER_FLOODED ) VectorSet( luxel, 0, 32, 255 ); VectorAdd( occludedSample, luxel, occludedSample ); occludedSamples += 1.0f; } /* normal luxel handling */ else if( luxel[ 3 ] > 0.0f ) { /* handle lit or flooded luxels */ if( *cluster > 0 || *cluster == CLUSTER_FLOODED ) { VectorAdd( sample, luxel, sample ); samples += luxel[ 3 ]; } /* handle occluded or unmapped luxels */ else { VectorAdd( occludedSample, luxel, occludedSample ); occludedSamples += luxel[ 3 ]; } } } } /* only use occluded samples if necessary */ if( samples <= 0.0f ) { VectorCopy( occludedSample, sample ); samples = occludedSamples; } /* store the sample back in super luxels */ luxel = SUPER_LUXEL( 0, x, y ); if( samples > 0.01f ) { VectorScale( sample, (1.0f / samples), luxel ); luxel[ 3 ] = 1.0f; } /* if any samples were mapped in any way, store ambient color */ else if( mappedSamples > 0 ) { VectorCopy( ambientColor, luxel ); luxel[ 3 ] = 1.0f; } /* store a bogus value to be fixed later */ else { VectorClear( luxel ); luxel[ 3 ] = -1.0f; } } } /* clean up and store into bsp luxels */ lm->used = 0; lm->twin = NULL; /* testing */ for( y = 0; y < lm->h; y++ ) { for( x = 0; x < lm->w; x++ ) { /* get luxels */ luxel = SUPER_LUXEL( 0, x, y ); /* is this a valid sample? */ if( luxel[ 3 ] > 0 ) { VectorCopy( luxel, sample ); samples = luxel[ 3 ]; numUsed++; lm->used++; } else { /* nick an average value from the neighbors */ VectorClear( sample ); samples = 0; /* fixme: why is this disabled?? */ for( sy = (y - 1); sy <= (y + 1); sy++ ) { if( sy < 0 || sy >= lm->h ) continue; for( sx = (x - 1); sx <= (x + 1); sx++ ) { if( sx < 0 || sx >= lm->w || (sx == x && sy == y) ) continue; /* get neighbor's particulars */ luxel = SUPER_LUXEL( 0, sx, sy ); if( luxel[ 3 ] < 0 ) continue; VectorAdd( sample, luxel, sample ); samples += luxel[ 3 ]; } } /* no samples? */ if( samples == 0.0f ) { VectorSet( sample, -1.0f, -1.0f, -1.0f ); samples = 1.0f; } else { numUsed++; lm->used++; } } /* scale the sample */ VectorScale( sample, (1.0f / samples), sample ); /* store the sample in the bsp luxels */ bspLuxel = BSP_LUXEL( 0, x, y ); if( sample[ 0 ] >= 0.0f ) VectorAdd( bspLuxel, sample, bspLuxel ); else VectorCopy( sample, bspLuxel ); /* store the sample in the radiosity luxels */ if( bounce > 0 ) { radLuxel = RAD_LUXEL( 0, x, y ); VectorCopy( sample, radLuxel ); } } } } /* ----------------------------------------------------------------- collapse non-unique lightmaps ----------------------------------------------------------------- */ if( noCollapse == qfalse ) { /* note it */ Sys_FPrintf( SYS_VRB, "collapsing..." ); /* set all twin refs to null */ for( i = 0; i < numRawLightmaps; i++ ) rawLightmaps[ i ].twin = NULL; /* walk the list of raw lightmaps */ numTwins = 0; numTwinLuxels = 0; for( i = 0; i < numRawLightmaps; i++ ) { /* get lightmap */ lm = &rawLightmaps[ i ]; if( lm->superLuxels == NULL ) continue; /* 2002-03-18: i suck. */ if( lm->twin != NULL ) continue; /* find all lightmaps that are virtually identical to this one */ for( j = i + 1; j < numRawLightmaps; j++ ) { /* get lightmap and early out if possible */ lm2 = &rawLightmaps[ j ]; if( lm2->twin != NULL ) continue; /* compare them */ if( CompareBSPLuxels( lm, lm2 ) ) { MergeBSPLuxels( lm, lm2 ); lm2->twin = lm; numTwins++; numTwinLuxels += (lm->w * lm->h); } } } } /* ----------------------------------------------------------------- sort raw lightmaps by shader ----------------------------------------------------------------- */ /* note it */ Sys_FPrintf( SYS_VRB, "sorting..." ); /* allocate a new sorted list */ if( sortLightmaps == NULL ) sortLightmaps = safe_malloc( numRawLightmaps * sizeof( int ) ); /* fill it out and sort it */ for( i = 0; i < numRawLightmaps; i++ ) sortLightmaps[ i ] = i; qsort( sortLightmaps, numRawLightmaps, sizeof( int ), CompareRawLightmap ); /* ----------------------------------------------------------------- allocate output lightmaps ----------------------------------------------------------------- */ /* note it */ Sys_FPrintf( SYS_VRB, "allocating..." ); /* kill all existing output lightmaps */ if( outLightmaps != NULL ) { for( i = 0; i < numOutLightmaps; i++ ) { free( outLightmaps[ i ].lightBits ); free( outLightmaps[ i ].bspLightBytes ); } free( outLightmaps ); outLightmaps = NULL; } numOutLightmaps = 0; numBSPLightmaps = 0; /* walk the list of surfaces */ for( i = 0; i < numRawLightmaps; i++ ) { /* get lightmap */ lm = &rawLightmaps[ sortLightmaps[ i ] ]; if( lm->twin != NULL ) continue; /* find an output lightmap */ FindOutLightmap( lm ); } /* walk the list of surfaces again, to account for twins */ for( i = 0; i < numRawLightmaps; i++ ) { /* get lightmap */ lm = &rawLightmaps[ sortLightmaps[ i ] ]; if( lm->twin == NULL ) continue; /* find output lightmap from twin */ lm->outLightmapNum = lm->twin->outLightmapNum; lm->lightmapX = lm->twin->lightmapX; lm->lightmapY = lm->twin->lightmapY; } /* ----------------------------------------------------------------- store output lightmaps ----------------------------------------------------------------- */ /* note it */ Sys_FPrintf( SYS_VRB, "storing..." ); /* count the bsp lightmaps and allocate space */ if( bspLightBytes != NULL ) free( bspLightBytes ); numBSPLightBytes = (numBSPLightmaps * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3); if( numBSPLightBytes == 0 ) bspLightBytes = NULL; else { bspLightBytes = safe_malloc( numBSPLightBytes ); memset( bspLightBytes, 0, numBSPLightBytes ); } /* walk the list of output lightmaps */ for( i = 0; i < numOutLightmaps; i++ ) { /* get output lightmap */ olm = &outLightmaps[ i ]; /* is this a valid bsp lightmap? */ if( olm->lightmapNum >= 0 ) { /* copy the lighting data */ lb = bspLightBytes + (olm->lightmapNum * LIGHTMAP_HEIGHT * LIGHTMAP_WIDTH * 3); memcpy( lb, olm->bspLightBytes, LIGHTMAP_HEIGHT * LIGHTMAP_WIDTH * 3 ); } else { char dirname[ MAX_QPATH ], filename[ MAX_QPATH ]; /* do some path mangling */ strcpy( dirname, source ); StripExtension( dirname ); /* make a directory for the lightmaps */ Q_mkdir( dirname ); /* write out the lightmap */ sprintf( filename, "%s/_lm_%03d.tga", dirname, i ); Sys_FPrintf( SYS_VRB, "\nwriting %s", filename ); WriteTGA24( filename, olm->bspLightBytes, olm->customWidth, olm->customHeight, qtrue ); } } if( numBSPLightmaps != numOutLightmaps ) Sys_FPrintf( SYS_VRB, "\n" ); /* ----------------------------------------------------------------- project the lightmaps onto the bsp surfaces ----------------------------------------------------------------- */ /* note it */ Sys_FPrintf( SYS_VRB, "projecting..." ); /* walk the list of surfaces */ for( i = 0; i < numBSPDrawSurfaces; i++ ) { /* get the surface and info */ ds = &bspDrawSurfaces[ i ]; info = &surfaceInfos[ i ]; olm = NULL; /* handle no lightmap case */ if( info->lm == NULL || info->lm->outLightmapNum < 0 ) ds->lightmapNum[ 0 ] = -3; else { /* get lightmaps */ lm = info->lm; olm = &outLightmaps[ info->lm->outLightmapNum ]; /* set bsp lightmap number */ ds->lightmapNum[ 0 ] = olm->lightmapNum; /* get some floating point love */ lmx = (float) lm->lightmapX / (float) olm->customWidth; lmy = (float) lm->lightmapY / (float) olm->customHeight; } /* get verts */ dv = &bspDrawVerts[ ds->firstVert ]; ydv = &yDrawVerts[ ds->firstVert ]; /* calc lightmap st coords and store lighting values */ for( j = 0; j < ds->numVerts; j++, dv++, ydv++ ) { /* store lightmap st coords (but don't overwrite if unnecessary) */ if( olm != NULL ) { dv->lightmap[ 0 ][ 0 ] = lmx + (ydv->lightmap[ 0 ][ 0 ] / (superSample * olm->customWidth)); dv->lightmap[ 0 ][ 1 ] = lmy + (ydv->lightmap[ 0 ][ 1 ] / (superSample * olm->customHeight)); } /* store vertex light */ luxel = VERTEX_LUXEL( 0, ds->firstVert + j ); ColorToBytes( luxel, dv->color[ 0 ], 1.0f ); } /* set the (possibly custom) shader for this surface */ if( olm == NULL || (olm->customWidth == LIGHTMAP_WIDTH && olm->customHeight == LIGHTMAP_HEIGHT) ) ds->shaderNum = EmitShader( info->si->shader ); else { shaderInfo_t *csi; char suffix[ 8 ], replace[ MAX_QPATH ]; /* do some name mangling */ sprintf( suffix, "%03d", info->lm->outLightmapNum ); sprintf( replace, "maps/%s/_lm_%03d.tga", mapName, info->lm->outLightmapNum ); /* create custom shader */ csi = CustomShader( info->si, mapName, suffix, "$lightmap", replace ); /* store it */ //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) ); ds->shaderNum = EmitShader( csi->shader ); //% Sys_Printf( ")\n" ); } } /* finish */ Sys_FPrintf( SYS_VRB, "done.\n" ); /* calc num stored */ numStored = numBSPLightBytes / 3; efficiency = (numUsed <= 0) ? 0 : (float) numUsed / (float) numStored; /* print stats */ Sys_Printf( "%9d luxels used\n", numUsed ); Sys_Printf( "%9d luxels stored (%3.2f%s efficiency)\n", numStored, efficiency * 100.0, "%%" ); /* stupid io lib */ Sys_Printf( "%9d identical surface lightmaps, using %d luxels\n", numTwins, numTwinLuxels ); Sys_Printf( "%9d vertex forced surfaces\n", numSurfsVertexForced ); Sys_Printf( "%9d vertex approximated surfaces\n", numSurfsVertexApproximated ); Sys_Printf( "%9d BSP lightmaps\n", numBSPLightmaps ); Sys_Printf( "%9d total lightmaps\n", numOutLightmaps ); /* write map shader file */ WriteMapShaderFile(); }