/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * ****/ // doom per-sector lighting #include "dlight.h" char source[MAX_PATH] = ""; vec3_t g_reflectivity[MAX_MAP_TEXTURES]; bool g_texture_init[MAX_MAP_TEXTURES]; vec_t g_direct_scale = DEFAULT_DLIGHT_SCALE; float g_maxlight = DEFAULT_LIGHTCLIP; // originally this was 196 vec_t g_gamma = DEFAULT_GAMMA; vec3_t g_ambient = { 0, 0, 0 }; dplane_t g_backplanes[MAX_MAP_PLANES]; // equal to MAX_MAP_FACES, there is no errors faceinfo_t g_faceinfo[MAX_MAP_FACES]; facelight_t g_facelight[MAX_MAP_FACES]; static char global_lights[MAX_PATH] = ""; static char level_lights[MAX_PATH] = ""; /* =================================================================== MISC =================================================================== */ const dplane_t *GetPlaneFromFace( const dface_t *face ) { ASSERT( face != NULL ); if( face->side ) return &g_backplanes[face->planenum]; return &g_dplanes[face->planenum]; } const dplane_t *GetPlaneFromFace( const uint facenum ) { ASSERT( facenum < MAX_MAP_FACES ); dface_t *face = &g_dfaces[facenum]; if( face->side ) return &g_backplanes[face->planenum]; return &g_dplanes[face->planenum]; } dleaf_t *PointInLeaf( const vec3_t point ) { int nodenum = 0; while( nodenum >= 0 ) { dnode_t *node = &g_dnodes[nodenum]; if( PlaneDiff( point, &g_dplanes[node->planenum] ) > 0 ) nodenum = node->children[0]; else nodenum = node->children[1]; } return &g_dleafs[-nodenum - 1]; } /* ============= MakeBackplanes ============= */ void MakeBackplanes( void ) { for( int i = 0; i < g_numplanes; i++ ) { VectorNegate( g_dplanes[i].normal, g_backplanes[i].normal ); g_backplanes[i].dist = -g_dplanes[i].dist; } } /* =================================================================== TEXTURE LIGHT VALUES =================================================================== */ static texlight_t g_texlights[MAX_TEXLIGHTS]; static int g_num_texlights; /* ============ ReadLightFile ============ */ void ReadLightFile( const char *filename, bool use_direct_path ) { int file_texlights = 0; char scan[128]; int j, argCnt; file_t *f; FS_AllowDirectPaths( use_direct_path ); f = FS_Open( filename, "r", false ); FS_AllowDirectPaths( false ); if( !f ) return; else MsgDev( D_INFO, "[Reading texlights from '%s']\n", filename ); while( FS_Gets( f, (byte *)&scan, sizeof( scan )) != EOF ) { char szTexlight[256]; vec_t r, g, b, i = 1; char *comment = scan; // skip the comments if( comment[0] == '/' && comment[1] == '/' ) continue; argCnt = sscanf( scan, "%s %f %f %f %f", szTexlight, &r, &g, &b, &i ); if( argCnt == 2 ) { // eith 1+1 args, the R,G,B values are all equal to the first value g = b = r; } else if( argCnt == 5 ) { // With 1 + 4 args, the R,G,B values are "scaled" by the fourth numeric value i; r *= i / 255.0; g *= i / 255.0; b *= i / 255.0; } else if( argCnt != 4 ) { if( Q_strlen( scan ) > 4 ) MsgDev( D_WARN, "ignoring bad texlight '%s' in %s", scan, filename ); continue; } for( j = 0; j < g_num_texlights; j++ ) { texlight_t *tl = &g_texlights[j]; if( !Q_strcmp( tl->name, szTexlight )) { if( !Q_strcmp( tl->filename, filename )) { MsgDev( D_REPORT, "duplication of '%s' in file '%s'!\n", tl->name, tl->filename ); } else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b ) { MsgDev( D_REPORT, "overriding '%s' from '%s' with '%s'!\n", tl->name, tl->filename, filename ); } else { MsgDev( D_WARN, "redundant '%s' def in '%s' AND '%s'!\n", tl->name, tl->filename, filename ); } break; } } Q_strncpy( g_texlights[j].name, szTexlight, sizeof( g_texlights[0].name )); VectorSet( g_texlights[j].value, r, g, b ); g_texlights[j].filename = filename; file_texlights++; g_num_texlights = Q_max( g_num_texlights, j + 1 ); if( g_num_texlights == MAX_TEXLIGHTS ) COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" ); } MsgDev( D_REPORT, "[%i texlights parsed from '%s']\n\n", file_texlights, filename ); FS_Close( f ); } /* ============ LightForTexture ============ */ void LightForTexture( const char *name, vec3_t result ) { VectorClear( result ); for( int i = 0; i < g_num_texlights; i++ ) { if( !Q_stricmp( name, g_texlights[i].name )) { VectorCopy( g_texlights[i].value, result ); MsgDev( D_REPORT, "Texture '%s': baselight is (%f,%f,%f).\n", name, result[0], result[1], result[2] ); return; } } } /* ============= BaseLightForFace ============= */ void BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity ) { int miptex = g_texinfo[f->texinfo].miptex; miptex_t *mt; // check for light emited by texture mt = GetTextureByMiptex( miptex ); if( !mt ) return; LightForTexture( mt->name, light ); VectorClear( reflectivity ); int samples = mt->width * mt->height; byte *pal = ((byte *)mt) + mt->offsets[0] + (((mt->width * mt->height) * 85) >> 6); byte *buf = ((byte *)mt) + mt->offsets[0]; vec3_t total; // check for cache if( g_texture_init[miptex] ) { VectorCopy( g_reflectivity[miptex], reflectivity ); return; } pal += sizeof( short ); // skip colorsize VectorClear( total ); for( int i = 0; i < samples; i++ ) { vec3_t reflectivity; if( mt->name[0] == '{' && buf[i] == 0xFF ) { VectorClear( reflectivity ); } else { int texel = buf[i]; reflectivity[0] = pow( pal[texel*3+0] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); reflectivity[1] = pow( pal[texel*3+1] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); reflectivity[2] = pow( pal[texel*3+2] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); VectorScale( reflectivity, DEFAULT_TEXREFLECTSCALE, reflectivity ); } VectorAdd( total, reflectivity, total ); } VectorScale( total, 1.0 / (double)(mt->width * mt->height), g_reflectivity[miptex] ); VectorCopy( g_reflectivity[miptex], reflectivity ); MsgDev( D_REPORT, "Texture '%s': reflectivity is (%f,%f,%f).\n", mt->name, reflectivity[0], reflectivity[1], reflectivity[2] ); g_texture_init[miptex] = true; } /* ============ BuildFaceInfos calc lightmap sizes ============ */ void BuildFaceInfos( void ) { int m, j, e; int facenum1; vec_t val, lmmins[2], lmmaxs[2]; float lmvecs[2][4]; dtexinfo_t *tex; const dplane_t *dp1; const dface_t *f1; faceinfo_t *fn; dvertex_t *v; // store a list of every face that uses a particular vertex for( facenum1 = 0; facenum1 < g_numfaces; facenum1++ ) { fn = &g_faceinfo[facenum1]; f1 = &g_dfaces[facenum1]; dp1 = GetPlaneFromFace( f1 ); tex = &g_texinfo[f1->texinfo]; VectorCopy( dp1->normal, fn->facenormal ); lmmins[0] = lmmins[1] = 999999; lmmaxs[0] = lmmaxs[1] =-999999; int max_surface_extent = GetSurfaceExtent( tex ); int texture_step = GetTextureStep( tex ); LightMatrixFromTexMatrix( tex, lmvecs ); for( j = 0; j < f1->numedges; j++ ) { e = g_dsurfedges[f1->firstedge + j]; if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]]; else v = &g_dvertexes[g_dedges[-e].v[1]]; for( m = 0; m < 2; m++ ) { /* The following calculation is sensitive to floating-point * precision. It needs to produce the same result that the * light compiler does, because R_BuildLightMap uses surf-> * extents to know the width/height of a surface's lightmap, * and incorrect rounding here manifests itself as patches * of "corrupted" looking lightmaps. * Most light compilers are win32 executables, so they use * x87 floating point. This means the multiplies and adds * are done at 80-bit precision, and the result is rounded * down to 32-bits and stored in val. * Adding the casts to double seems to be good enough to fix * lighting glitches when Quakespasm is compiled as x86_64 * and using SSE2 floating-point. A potential trouble spot * is the hallway at the beginning of mfxsp17. -- ericw */ val = ((double)v->point[0] * (double)lmvecs[m][0]) + ((double)v->point[1] * (double)lmvecs[m][1]) + ((double)v->point[2] * (double)lmvecs[m][2]) + (double)lmvecs[m][3]; lmmins[m] = Q_min( val, lmmins[m] ); lmmaxs[m] = Q_max( val, lmmaxs[m] ); } } // calc face extents for traceline and lightmap extents for LightForPoint for( j = 0; j < 2; j++ ) { lmmins[j] = floor( lmmins[j] / texture_step ); lmmaxs[j] = ceil( lmmaxs[j] / texture_step ); fn->texmins[j] = lmmins[j]; fn->texsize[j] = (lmmaxs[j] - lmmins[j]); } if( !FBitSet( tex->flags, TEX_SPECIAL )) { if( fn->texsize[0] * fn->texsize[1] > ( MAX_SINGLEMAP / 3 )) COM_FatalError( "surface to large to map\n" ); if( fn->texsize[0] > max_surface_extent ) MsgDev( D_ERROR, "bad surface extents %d > %d\n", fn->texsize[0], max_surface_extent ); if( fn->texsize[1] > max_surface_extent ) MsgDev( D_ERROR, "bad surface extents %d > %d\n", fn->texsize[1], max_surface_extent ); if( fn->texsize[0] < 0 || fn->texsize[1] < 0 ) COM_FatalError( "negative extents\n" ); } } } /* ============= BuildSectorLights ============= */ void BuildSectorLights( int facenum, int thread ) { facelight_t *fl = &g_facelight[facenum]; int lmwidth, lmheight; word sectorlight; vec_t lightvalue; int i, j; faceinfo_t *fn; dface_t *f; sample_t *s; f = &g_dfaces[facenum]; // some surfaces don't need lightmaps f->lightofs = -1; for( j = 0; j < MAXLIGHTMAPS; j++ ) f->styles[j] = 255; if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) return; // non-lit texture // decode per-sector lighting values sectorlight = GetSurfaceGroupId( f ); f->styles[0] = ((sectorlight & 0xFF00) >> 8); lightvalue = (vec_t)(sectorlight & 0x00FF); fn = &g_faceinfo[facenum]; lmwidth = fn->texsize[0] + 1; lmheight = fn->texsize[1] + 1; fl->numsamples = lmwidth * lmheight; // alloc lightmap for this face fl->samples = (sample_t *)Mem_Alloc( fl->numsamples * sizeof( sample_t )); // doom lighting it's easy. just fill face lightmap with single color from sector // TODO: add lighting from self-illuminated textures and reflecitivity? for( i = 0; i < fl->numsamples; i++ ) { for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) { VectorFill( fl->samples[i].light[j], lightvalue ); } } // add an ambient term if desired if( g_ambient[0] || g_ambient[1] || g_ambient[2] ) { for( j = 0; j < MAXLIGHTMAPS && f->styles[j] == 255; j++ ); if( j == MAXLIGHTMAPS ) f->styles[0] = 0; // adding style for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) { if( f->styles[j] == 0 ) { s = fl->samples; for( i = 0; i < fl->numsamples; i++, s++ ) VectorAdd( s->light[j], g_ambient, s->light[j] ); break; } } } } /* ============ ScaleDirectLights ============ */ void ScaleDirectLights( void ) { sample_t *samp; facelight_t *fl; dface_t *f; for( int facenum = 0; facenum < g_numfaces; facenum++ ) { f = &g_dfaces[facenum]; if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) continue; fl = &g_facelight[facenum]; for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) { for( int i = 0; i < fl->numsamples; i++ ) { samp = &fl->samples[i]; VectorScale( samp->light[k], g_direct_scale, samp->light[k] ); } } } } void PrecompLightmapOffsets( void ) { int lightstyles; facelight_t *fl; dface_t *f; g_shadowdatasize = 0; g_normaldatasize = 0; g_lightdatasize = 0; for( int facenum = 0; facenum < g_numfaces; facenum++ ) { f = &g_dfaces[facenum]; fl = &g_facelight[facenum]; if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) continue; // non-lit texture for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) { if( f->styles[lightstyles] == 255 ) break; // end if styles } if( !lightstyles ) continue; f->lightofs = g_lightdatasize; g_lightdatasize += fl->numsamples * 3 * lightstyles; } g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize ); } void FinalLightFace( int facenum, int threadnum ) { float minlight = 0.0f; // TODO: allow minlight? int lightstyles; int i, j, k; sample_t *samp; dtexinfo_t *tx; facelight_t *fl; dface_t *f; vec3_t lb; f = &g_dfaces[facenum]; fl = &g_facelight[facenum]; tx = &g_texinfo[f->texinfo]; if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) return; // non-lit texture for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) { if( f->styles[lightstyles] == 255 ) break; } if( !lightstyles ) return; for( k = 0; k < lightstyles; k++ ) { samp = fl->samples; for( j = 0; j < fl->numsamples; j++, samp++ ) { VectorCopy( samp->light[k], lb ); // clip from the bottom first lb[0] = Q_max( lb[0], minlight ); lb[1] = Q_max( lb[1], minlight ); lb[2] = Q_max( lb[2], minlight ); // clip from the top if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) { // find max value and scale the whole color down; float max = VectorMax( lb ); for( i = 0; i < 3; i++ ) lb[i] = ( lb[i] * g_maxlight ) / max; } // do gamma adjust lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = Q_rint( lb[0] ); g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = Q_rint( lb[1] ); g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = Q_rint( lb[2] ); } } } void FreeFaceLights( void ) { for( int i = 0; i < g_numfaces; i++ ) { Mem_Free( g_facelight[i].samples ); } } //============================================================== /* ============= DoomLightWorld ============= */ void DoomLightWorld( void ) { MakeBackplanes(); BuildFaceInfos(); // build initial facelights RunThreadsOnIndividual( g_numfaces, true, BuildSectorLights ); ScaleDirectLights(); PrecompLightmapOffsets(); RunThreadsOnIndividual( g_numfaces, true, FinalLightFace ); FreeFaceLights(); } /* ============ PrintDlightSettings show compiler settings like ZHLT ============ */ static void PrintDlightSettings( void ) { char buf1[1024]; char buf2[1024]; Msg( "\nCurrent dlight settings\n" ); Msg( "Name | Setting | Default\n" ); Msg( "---------------------|-----------|-------------------------\n" ); Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_direct_scale ); Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_DLIGHT_SCALE ); Msg( "direct light scale [ %7s ] [ %7s ]\n", buf1, buf2 ); Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_gamma ); Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_GAMMA ); Msg( "gamma factor [ %7s ] [ %7s ]\n", buf1, buf2 ); Msg( "\n" ); } /* ============ PrintDlightUsage show compiler usage like ZHLT ============ */ static void PrintDlightUsage( void ) { Msg( "\n-= dlight Options =-\n\n" ); Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); Msg( " -threads # : manually specify the number of threads to run\n" ); Msg( " -ambient r g b : set ambient world light (0.0 to 1.0, r g b)\n" ); Msg( " -dscale : direct light scaling factor\n" ); Msg( " -gamma : set global gamma value\n" ); Msg( " bspfile : The bspfile to compile\n\n" ); exit( 1 ); } /* ======== main light modelfile ======== */ int main( int argc, char **argv ) { double start, end; char str[64]; int i; atexit( Sys_CloseLog ); source[0] = '\0'; for( i = 1; i < argc; i++ ) { if( !Q_strcmp( argv[i], "-dev" )) { SetDeveloperLevel( atoi( argv[i+1] )); i++; } else if( !Q_strcmp( argv[i], "-threads" )) { g_numthreads = atoi( argv[i+1] ); i++; } else if( !Q_strcmp( argv[i], "-ambient" )) { if( argc > ( i + 3 )) { g_ambient[0] = (float)atof( argv[i+1] ) * 0.5f; g_ambient[1] = (float)atof( argv[i+2] ) * 0.5f; g_ambient[2] = (float)atof( argv[i+3] ) * 0.5f; i += 3; } else { break; } } else if( !Q_strcmp( argv[i], "-dscale" )) { g_direct_scale = (float)atof( argv[i+1] ); i++; } else if( !Q_strcmp( argv[i], "-gamma" )) { g_gamma = (float)atof( argv[i+1] ); i++; } else if( argv[i][0] == '-' ) { MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); break; } else if( !source[0] ) { Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); COM_StripExtension( source ); } else { MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); break; } } if( i != argc || !source[0] ) { if( !source[0] ) Msg( "no mapfile specified\n" ); PrintDlightUsage(); } start = I_FloatTime (); Sys_InitLogAppend( va( "%s.log", source )); Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); PrintDlightSettings(); ThreadSetDefault (); // starting base filesystem FS_Init( source ); // Set the required global lights filename // try looking in the directory we were run from GetModuleFileName( NULL, global_lights, sizeof( global_lights )); COM_ExtractFilePath( global_lights, global_lights ); Q_strncat( global_lights, "\\lights.rad", sizeof( global_lights )); // Set the optional level specific lights filename COM_FileBase( source, str ); Q_snprintf( level_lights, sizeof( level_lights ), "maps/%s.rad", str ); if( !FS_FileExists( level_lights, false )) level_lights[0] = '\0'; ReadLightFile( global_lights, true ); // Required if( *level_lights ) ReadLightFile( level_lights, false ); // Optional & implied COM_DefaultExtension( source, ".bsp" ); MsgDev( D_INFO, "\n" ); LoadBSPFile( source ); if( g_nummodels <= 0 ) COM_FatalError( "map %s without any models\n", source ); ParseEntities(); TEX_LoadTextures(); // keep it in acceptable range g_gamma = bound( 0.5, g_gamma, 2.0 ); DoomLightWorld (); WriteBSPFile( source ); TEX_FreeTextures (); FreeEntities (); FS_Shutdown(); SetDeveloperLevel( D_REPORT ); Mem_Check(); end = I_FloatTime (); Q_timestring((int)( end - start ), str ); Msg( "%s elapsed\n", str ); return 0; }