diff --git a/q3map2/q3map2/bspfile_ibsp.cpp b/q3map2/q3map2/bspfile_ibsp.cpp index 75a1128..e2f6a11 100755 --- a/q3map2/q3map2/bspfile_ibsp.cpp +++ b/q3map2/q3map2/bspfile_ibsp.cpp @@ -61,16 +61,8 @@ #define LUMP_LIGHTMAPS 14 #define LUMP_LIGHTGRID 15 #define LUMP_VISIBILITY 16 -#define LUMP_ADVERTISEMENTS 17 -#define LUMP_LIGHTGRID_SH 18 - #define HEADER_LUMPS_V46 17 /* standard Q3: lumps 0-16, header = 144 bytes */ -#define HEADER_LUMPS_V47 19 /* extended: lumps 0-18, header = 160 bytes */ -#define HEADER_LUMPS_MAX 19 /* max for internal use */ - #define IBSP_VERSION_V46 46 -#define IBSP_VERSION_V47 47 - /* types */ struct ibspHeader_v46_t @@ -80,20 +72,8 @@ struct ibspHeader_v46_t bspLump_t lumps[ HEADER_LUMPS_V46 ]; }; -struct ibspHeader_v47_t -{ - char ident[ 4 ]; - int version; - bspLump_t lumps[ HEADER_LUMPS_V47 ]; -}; - -/* internal header -- always uses max lump count for convenience */ -struct ibspHeader_t -{ - char ident[ 4 ]; - int version; - bspLump_t lumps[ HEADER_LUMPS_MAX ]; -}; +/* internal header -- uses v46 lump count */ +using ibspHeader_t = ibspHeader_v46_t; @@ -238,30 +218,21 @@ void LoadIBSPFile( const char *filename ){ /* load the file */ MemBuffer file = LoadFile( filename ); - /* read ident and version to determine header size */ + /* read ident and version */ const byte *data = (const byte *)file.data(); + const size_t fileSize = file.size(); const int version = LittleLong( *(const int *)( data + 4 ) ); /* make sure it matches the format we're trying to load */ if ( !force && memcmp( data, g_game->bspIdent, 4 ) ) { Error( "%s is not a %s file", filename, g_game->bspIdent ); } - if ( !force && version != IBSP_VERSION_V46 && version != IBSP_VERSION_V47 ) { - Error( "%s is version %d, expected %d or %d", filename, version, IBSP_VERSION_V46, IBSP_VERSION_V47 ); + if ( !force && version != IBSP_VERSION_V46 ) { + Error( "%s is version %d, expected %d", filename, version, IBSP_VERSION_V46 ); } - /* determine lump count from version */ - const int numLumps = ( version == IBSP_VERSION_V47 ) ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46; - - /* CopyLump uses (byte*)header + offset to access file data, - so we overlay a bspHeader_t at the file buffer start and - copy/swap the lump directory into it. bspHeader_t has lumps[100] - so it can hold all our lumps. */ bspHeader_t *header = (bspHeader_t *)file.data(); - /* swap just the lump directory entries we have */ - SwapBlock( (int *)( (byte *)header + 8 ), numLumps * sizeof( bspLump_t ) ); - /* note: lumps beyond numLumps are not zeroed -- they overlap file data - in the buffer. We only access extended lumps under version checks. */ + SwapBlock( (int *)( (byte *)header + 8 ), HEADER_LUMPS_V46 * sizeof( bspLump_t ) ); /* load/convert standard lumps (0-16) */ CopyLump( header, LUMP_SHADERS, bspShaders ); @@ -282,33 +253,81 @@ void LoadIBSPFile( const char *filename ){ CopyLump( header, LUMP_ENTITIES, bspEntData ); CopyLump( header, LUMP_LIGHTGRID, bspGridPoints ); - /* v47 extended lumps (17-18) */ - if ( version == IBSP_VERSION_V47 ) { - /* advertisements */ - CopyLump( header, LUMP_ADVERTISEMENTS, bspAds ); + /* BRX1 extension detection -- two-pass minimum probe */ + bspGridPointsSH.clear(); + { + const int extStart = BSP_V46_HEADER_SIZE; - /* SH light grid */ - const int length = header->lumps[LUMP_LIGHTGRID_SH].length; - const int offset = header->lumps[LUMP_LIGHTGRID_SH].offset; - if ( length > (int)sizeof( bspGridSHHeader_t ) ) { - const bspGridSHHeader_t *shHeader = (const bspGridSHHeader_t *)( data + offset ); - gridMinsSH = shHeader->gridMins; - gridSizeSH = shHeader->gridSize; - gridBoundsSH[0] = shHeader->gridBounds[0]; - gridBoundsSH[1] = shHeader->gridBounds[1]; - gridBoundsSH[2] = shHeader->gridBounds[2]; - const int numPoints = shHeader->numPoints; - const bspGridPointSH_t *points = (const bspGridPointSH_t *)( data + offset + sizeof( bspGridSHHeader_t ) ); - bspGridPointsSH.assign( points, points + numPoints ); + /* pass 1: check no standard lump overlaps [extStart, extStart+8) */ + bool canProbe = ( fileSize >= (size_t)extStart + 8 ); + if ( canProbe ) { + for ( int i = 0; i < HEADER_LUMPS_V46; ++i ) { + const int lo = header->lumps[i].offset; + const int llen = header->lumps[i].length; + if ( llen > 0 && lo < extStart + 8 && lo + llen > extStart ) { + canProbe = false; + break; + } + } } - else { - bspGridPointsSH.clear(); + + if ( canProbe ) { + const int magic = LittleLong( *(const int *)( data + extStart ) ); + const int extCount = LittleLong( *(const int *)( data + extStart + 4 ) ); + + if ( magic == BSP_EXT_MAGIC_INT && extCount > 0 && extCount <= BSP_EXT_MAX_LUMPS ) { + const int extEnd = extStart + 8 + extCount * (int)sizeof( bspLump_t ); + + /* pass 2: verify full extension block range */ + bool extValid = ( fileSize >= (size_t)extEnd ); + if ( extValid ) { + for ( int i = 0; i < HEADER_LUMPS_V46; ++i ) { + const int lo = header->lumps[i].offset; + const int llen = header->lumps[i].length; + if ( llen > 0 && lo < extEnd && lo + llen > extStart ) { + extValid = false; + break; + } + } + } + + if ( extValid ) { + /* read extension lump directory */ + const bspLump_t *extLumps = (const bspLump_t *)( data + extStart + 8 ); + + /* EXT_LUMP_LIGHTGRID_SH */ + if ( extCount > EXT_LUMP_LIGHTGRID_SH ) { + const int off = LittleLong( extLumps[EXT_LUMP_LIGHTGRID_SH].offset ); + const int len = LittleLong( extLumps[EXT_LUMP_LIGHTGRID_SH].length ); + if ( len > (int)sizeof( bspGridSHHeader_t ) && off + len <= (int)fileSize ) { + const bspGridSHHeader_t *shHeader = (const bspGridSHHeader_t *)( data + off ); + gridMinsSH = shHeader->gridMins; + gridSizeSH = shHeader->gridSize; + gridBoundsSH[0] = shHeader->gridBounds[0]; + gridBoundsSH[1] = shHeader->gridBounds[1]; + gridBoundsSH[2] = shHeader->gridBounds[2]; + const int numPoints = shHeader->numPoints; + const bspGridPointSH_t *points = (const bspGridPointSH_t *)( data + off + sizeof( bspGridSHHeader_t ) ); + bspGridPointsSH.assign( points, points + numPoints ); + } + } + + /* EXT_LUMP_LIGHTMAP_INFO */ + if ( extCount > EXT_LUMP_LIGHTMAP_INFO ) { + const int off = LittleLong( extLumps[EXT_LUMP_LIGHTMAP_INFO].offset ); + const int len = LittleLong( extLumps[EXT_LUMP_LIGHTMAP_INFO].length ); + if ( len >= (int)sizeof( bspLightmapInfo_t ) && off + len <= (int)fileSize ) { + const bspLightmapInfo_t *lmInfo = (const bspLightmapInfo_t *)( data + off ); + bspLightmapPageW = LittleLong( lmInfo->pageWidth ); + bspLightmapPageH = LittleLong( lmInfo->pageHeight ); + } + } + + Sys_FPrintf( SYS_VRB, "BRX1 extension: %d lumps\n", extCount ); + } + } } } - else { - bspAds.clear(); - bspGridPointsSH.clear(); - } } /* @@ -327,16 +346,12 @@ void LoadIBSPorRBSPFilePartially( const char *filename ){ if ( !force && memcmp( data, g_game->bspIdent, 4 ) ) { Error( "%s is not a %s file", filename, g_game->bspIdent ); } - if ( !force && version != IBSP_VERSION_V46 && version != IBSP_VERSION_V47 ) { - Error( "%s is version %d, expected %d or %d", filename, version, IBSP_VERSION_V46, IBSP_VERSION_V47 ); + if ( !force && version != IBSP_VERSION_V46 ) { + Error( "%s is version %d, expected %d", filename, version, IBSP_VERSION_V46 ); } - const int numLumps = ( version == IBSP_VERSION_V47 ) ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46; - bspHeader_t *header = (bspHeader_t *)file.data(); - SwapBlock( (int *)( (byte *)header + 8 ), numLumps * sizeof( bspLump_t ) ); - for ( int i = numLumps; i < 100; ++i ) - header->lumps[i] = {}; + SwapBlock( (int *)( (byte *)header + 8 ), HEADER_LUMPS_V46 * sizeof( bspLump_t ) ); /* load/convert lumps */ CopyLump( header, LUMP_SHADERS, bspShaders ); @@ -355,23 +370,33 @@ void LoadIBSPorRBSPFilePartially( const char *filename ){ */ void WriteIBSPFile( const char *filename ){ - /* determine version: v47 if SH data exists, v46 otherwise */ - const bool extendedBSP = !bspGridPointsSH.empty(); - const int bspVersion = extendedBSP ? IBSP_VERSION_V47 : IBSP_VERSION_V46; - const int numLumps = extendedBSP ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46; - const int headerSize = 8 + numLumps * (int)sizeof( bspLump_t ); + /* always write v46 -- extensions go in BRX1 block after the header */ + const bool hasSHGrid = !bspGridPointsSH.empty(); + const bool hasLmInfo = ( bspLightmapPageW != LIGHTMAP_WIDTH || bspLightmapPageH != LIGHTMAP_HEIGHT ); + const bool hasExtension = hasSHGrid || hasLmInfo; ibspHeader_t header{}; - //% Swapfile(); - - /* set up header */ + /* set up header -- always v46 */ memcpy( header.ident, g_game->bspIdent, 4 ); - header.version = LittleLong( bspVersion ); + header.version = LittleLong( IBSP_VERSION_V46 ); - /* write initial header (placeholder, overwritten at the end) */ + const int v46HeaderSize = (int)sizeof( ibspHeader_v46_t ); + + /* compute extension block size */ + int extBlockSize = 0; + if ( hasExtension ) { + extBlockSize = 8 + EXT_LUMP_COUNT * (int)sizeof( bspLump_t ); /* magic + count + directory */ + } + + /* write initial header + extension placeholder (overwritten at the end) */ FILE *file = SafeOpenWrite( filename ); - SafeWrite( file, &header, headerSize ); + SafeWrite( file, &header, v46HeaderSize ); + if ( hasExtension ) { + /* placeholder for extension block -- filled in at the end */ + std::vector extPlaceholder( extBlockSize, 0 ); + SafeWrite( file, extPlaceholder.data(), extBlockSize ); + } { /* add marker lump */ time_t t; @@ -381,7 +406,7 @@ void WriteIBSPFile( const char *filename ){ AddLump( file, header.lumps[0], std::vector( marker.cbegin(), marker.cend() + 1 ) ); } - /* add standard lumps (0-16) */ + /* add standard lumps (0-16) -- data starts after header + extension block */ AddLump( file, header.lumps[LUMP_SHADERS], bspShaders ); AddLump( file, header.lumps[LUMP_PLANES], bspPlanes ); AddLump( file, header.lumps[LUMP_LEAFS], bspLeafs ); @@ -400,34 +425,52 @@ void WriteIBSPFile( const char *filename ){ AddLump( file, header.lumps[LUMP_FOGS], bspFogs ); AddLump( file, header.lumps[LUMP_DRAWINDEXES], bspDrawIndexes ); - /* v47 extended lumps (17-18) */ - if ( extendedBSP ) { - /* advertisements */ - AddLump( file, header.lumps[LUMP_ADVERTISEMENTS], bspAds ); + /* BRX1 extension lumps -- data appended after standard lumps */ + bspLump_t extLumps[EXT_LUMP_COUNT] = {}; + if ( hasExtension ) { + /* SH light grid */ + if ( hasSHGrid ) { + const size_t shHeaderSz = sizeof( bspGridSHHeader_t ); + const size_t dataSz = bspGridPointsSH.size() * sizeof( bspGridPointSH_t ); + std::vector shLump( shHeaderSz + dataSz ); + bspGridSHHeader_t shHeader; + shHeader.gridMins = gridMinsSH; + shHeader.gridSize = gridSizeSH; + shHeader.gridBounds[0] = gridBoundsSH[0]; + shHeader.gridBounds[1] = gridBoundsSH[1]; + shHeader.gridBounds[2] = gridBoundsSH[2]; + shHeader.numPoints = (int)bspGridPointsSH.size(); + memcpy( shLump.data(), &shHeader, shHeaderSz ); + memcpy( shLump.data() + shHeaderSz, bspGridPointsSH.data(), dataSz ); + AddLump( file, extLumps[EXT_LUMP_LIGHTGRID_SH], shLump ); + } - /* SH light grid -- header + points packed into a byte lump */ - const size_t shHeaderSz = sizeof( bspGridSHHeader_t ); - const size_t dataSz = bspGridPointsSH.size() * sizeof( bspGridPointSH_t ); - std::vector shLump( shHeaderSz + dataSz ); - bspGridSHHeader_t shHeader; - shHeader.gridMins = gridMinsSH; - shHeader.gridSize = gridSizeSH; - shHeader.gridBounds[0] = gridBoundsSH[0]; - shHeader.gridBounds[1] = gridBoundsSH[1]; - shHeader.gridBounds[2] = gridBoundsSH[2]; - shHeader.numPoints = (int)bspGridPointsSH.size(); - memcpy( shLump.data(), &shHeader, shHeaderSz ); - memcpy( shLump.data() + shHeaderSz, bspGridPointsSH.data(), dataSz ); - AddLump( file, header.lumps[LUMP_LIGHTGRID_SH], shLump ); + /* lightmap info */ + if ( hasLmInfo ) { + bspLightmapInfo_t lmInfo; + lmInfo.pageWidth = bspLightmapPageW; + lmInfo.pageHeight = bspLightmapPageH; + AddLump( file, extLumps[EXT_LUMP_LIGHTMAP_INFO], std::vector( 1, lmInfo ) ); + } } /* emit bsp size */ const int size = ftell( file ); Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / ( 1024 * 1024 ), size ); - /* write the completed header */ + /* write the completed v46 header */ fseek( file, 0, SEEK_SET ); - SafeWrite( file, &header, headerSize ); + SafeWrite( file, &header, v46HeaderSize ); + + /* write the completed BRX1 extension block */ + if ( hasExtension ) { + const int extMagic = LittleLong( BSP_EXT_MAGIC_INT ); + const int extCount = LittleLong( EXT_LUMP_COUNT ); + SafeWrite( file, &extMagic, 4 ); + SafeWrite( file, &extCount, 4 ); + SafeWrite( file, extLumps, EXT_LUMP_COUNT * sizeof( bspLump_t ) ); + Sys_FPrintf( SYS_VRB, "BRX1 extension: %d lumps written\n", EXT_LUMP_COUNT ); + } /* close the file */ fclose( file ); diff --git a/q3map2/q3map2/light.cpp b/q3map2/q3map2/light.cpp index 89d1a57..7fa7fd8 100755 --- a/q3map2/q3map2/light.cpp +++ b/q3map2/q3map2/light.cpp @@ -2722,12 +2722,18 @@ int LightMain( Args& args ){ } Sys_Printf( "Default lightmap size set to %d x %d pixels\n", lmCustomSizeW, lmCustomSizeH ); - /* enable external lightmaps */ + /* handle non-default lightmap size */ if ( lmCustomSizeW != g_game->lightmapSize || lmCustomSizeH != g_game->lightmapSize ) { - /* -lightmapsize might just require -external for native external lms, but it has already been used in existing batches alone, - so brand new switch here for external lms, referenced by shaders hack/behavior */ - externalLightmaps = !extlmhack; - Sys_Printf( "Storing all lightmaps externally\n" ); + if ( extlmhack ) { + /* -extlmhacksize: external TGA + custom shader hack (legacy, for vanilla Q3 engines) */ + Sys_Printf( "External lightmap hack enabled\n" ); + } + else { + /* -lightmapsize: store natively in BSP via BRX1 extension */ + bspLightmapPageW = lmCustomSizeW; + bspLightmapPageH = lmCustomSizeH; + Sys_Printf( "Lightmap pages stored in BSP at %d x %d (BRX1 extension)\n", bspLightmapPageW, bspLightmapPageH ); + } } } diff --git a/q3map2/q3map2/lightmaps_ydnar.cpp b/q3map2/q3map2/lightmaps_ydnar.cpp index 1342892..9e3ff6d 100755 --- a/q3map2/q3map2/lightmaps_ydnar.cpp +++ b/q3map2/q3map2/lightmaps_ydnar.cpp @@ -1741,7 +1741,9 @@ static void SetupOutLightmap( rawLightmap_t *lm, outLightmap_t *olm ){ } /* is this a "normal" bsp-stored lightmap? */ - if ( ( lm->customWidth == g_game->lightmapSize && lm->customHeight == g_game->lightmapSize ) || externalLightmaps ) { + if ( ( lm->customWidth == g_game->lightmapSize && lm->customHeight == g_game->lightmapSize ) + || ( lm->customWidth == bspLightmapPageW && lm->customHeight == bspLightmapPageH ) + || externalLightmaps ) { olm->lightmapNum = numBSPLightmaps; numBSPLightmaps++; @@ -2887,7 +2889,7 @@ void StoreSurfaceLightmaps( bool fastAllocate, bool storeForReal ){ timer.start(); /* count the bsp lightmaps and allocate space */ - const size_t gameLmSize = g_game->lightmapSize * g_game->lightmapSize * sizeof( Vector3b ); + const size_t gameLmSize = (size_t)lmCustomSizeW * lmCustomSizeH * sizeof( Vector3b ); if ( numBSPLightmaps == 0 || externalLightmaps ) { bspLightBytes.clear(); } @@ -3221,8 +3223,10 @@ void StoreSurfaceLightmaps( bool fastAllocate, bool storeForReal ){ } /* devise a custom shader for this surface (fixme: make this work with light styles) */ + /* skip this hack when using BRX1 native lightmap pages */ else if ( olm != nullptr && lm != nullptr && !externalLightmaps && - ( olm->customWidth != g_game->lightmapSize || olm->customHeight != g_game->lightmapSize ) ) { + ( olm->customWidth != g_game->lightmapSize || olm->customHeight != g_game->lightmapSize ) && + ( olm->customWidth != bspLightmapPageW || olm->customHeight != bspLightmapPageH ) ) { /* get output lightmap */ olm = &outLightmaps[ lm->outLightmapNums[ 0 ] ]; diff --git a/q3map2/q3map2/q3map2.h b/q3map2/q3map2/q3map2.h index b9709ab..1afd4b7 100755 --- a/q3map2/q3map2/q3map2.h +++ b/q3map2/q3map2/q3map2.h @@ -364,7 +364,27 @@ struct bspGridPoint_t byte latLong[ 2 ]; }; -/* SH light grid header -- stored at the start of LUMP_LIGHTGRID_SH */ +/* BRX1 backward-compatible BSP extension format. + Extension block sits at offset 144 (right after v46 header). + Layout: magic(4) + count(4) + lump_dir(count*8) */ +#define BSP_EXT_MAGIC "BRX1" +#define BSP_EXT_MAGIC_INT (('1'<<24)+('X'<<16)+('R'<<8)+'B') /* little-endian "BRX1" */ +#define BSP_V46_HEADER_SIZE 144 +#define BSP_EXT_MAX_LUMPS 32 + +/* Extension lump indices */ +#define EXT_LUMP_LIGHTGRID_SH 0 +#define EXT_LUMP_LIGHTMAP_INFO 1 +#define EXT_LUMP_COUNT 2 /* total extension lumps we use */ + +/* Lightmap page size metadata -- stored in EXT_LUMP_LIGHTMAP_INFO */ +struct bspLightmapInfo_t +{ + int pageWidth; + int pageHeight; +}; + +/* SH light grid header -- stored at the start of EXT_LUMP_LIGHTGRID_SH */ struct bspGridSHHeader_t { Vector3 gridMins; /* world-space origin of the SH grid */ @@ -2166,6 +2186,8 @@ inline bool exportLightmaps; inline bool externalLightmaps; inline int lmCustomSizeW = LIGHTMAP_WIDTH; inline int lmCustomSizeH = LIGHTMAP_WIDTH; +inline int bspLightmapPageW = LIGHTMAP_WIDTH; /* actual page size written to BSP (for BRX1 extension) */ +inline int bspLightmapPageH = LIGHTMAP_HEIGHT; inline const char * lmCustomDir; inline int lmLimitSize;