From a719e012fe45ff0c4d0006e7f64b84cf0a67d5f6 Mon Sep 17 00:00:00 2001 From: Garux Date: Wed, 13 Oct 2021 22:50:43 +0300 Subject: [PATCH] * -mergebsp [options] : Inject latter BSP to former. Tree and vis data of the main one are preserved. * -mergebsp -fixnames: Make incoming BSP target/targetname names unique to not collide with existing names * -mergebsp -world: Also merge worldspawn model (brushes as if they were detail, no BSP tree is affected) (only merges entities by default) --- ...mplete_list_of_command_line_parameters.htm | 9 + tools/quake3/q3map2/convert_bsp.cpp | 282 ++++++++++++++++++ tools/quake3/q3map2/help.cpp | 13 + tools/quake3/q3map2/main.cpp | 5 + tools/quake3/q3map2/q3map2.h | 1 + 5 files changed, 310 insertions(+) diff --git a/docs/Complete_list_of_command_line_parameters.htm b/docs/Complete_list_of_command_line_parameters.htm index b1f78a82..9f5caae6 100644 --- a/docs/Complete_list_of_command_line_parameters.htm +++ b/docs/Complete_list_of_command_line_parameters.htm @@ -429,6 +429,15 @@ td.formatted_questions ol { margin-top: 0px; margin-bottom: 0px; } +

BSP merge

+
    +
  • -mergebsp ... mainBsp.bsp bspToinject.bsp: Inject latter BSP to former. Tree and vis data of the main one are preserved.
  • +
  • -fixnames: Make incoming BSP target/targetname names unique to not collide with existing names
  • +
  • -world: Also merge worldspawn model (brushes as if they were detail, no BSP tree is affected) (only merges entities by default)
  • +
+ + + diff --git a/tools/quake3/q3map2/convert_bsp.cpp b/tools/quake3/q3map2/convert_bsp.cpp index b8d5ce17..6ef0ec7b 100644 --- a/tools/quake3/q3map2/convert_bsp.cpp +++ b/tools/quake3/q3map2/convert_bsp.cpp @@ -664,6 +664,288 @@ int ShiftBSPMain( Args& args ){ } +/* + MergeBSPMain() + merges two bsps + */ + +int MergeBSPMain( Args& args ){ + /* arg checking */ + if ( args.size() < 2 ) { + Sys_Printf( "Usage: q3map2 [-v] -mergebsp [-fixnames] [-world] \n" ); + return 0; + } + + const char *fileName2 = args.takeBack(); + const char *fileName1 = args.takeBack(); + const auto argsToInject = args.getVector(); + + const bool fixnames = args.takeArg( "-fixnames" ); + const bool addworld = args.takeArg( "-world" ); + + /* do some path mangling */ + strcpy( source, ExpandArg( fileName2 ) ); + path_set_extension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + + struct bsp + { + std::vector entities; + std::vector bspModels; + std::vector bspShaders; + std::vector bspLeafs; + std::vector bspPlanes; + std::vector bspNodes; + std::vector bspLeafSurfaces; + std::vector bspLeafBrushes; + std::vector bspBrushes; + std::vector bspBrushSides; + std::vector bspLightBytes; + std::vector bspGridPoints; + std::vector bspVisBytes; + std::vector bspDrawVerts; + std::vector bspDrawIndexes; + std::vector bspDrawSurfaces; + std::vector bspFogs; + } bsp; + + bsp.entities = std::move( entities ); + bsp.bspModels = std::move( bspModels ); + bsp.bspShaders = std::move( bspShaders ); + bsp.bspLeafs = std::move( bspLeafs ); + bsp.bspPlanes = std::move( bspPlanes ); + bsp.bspNodes = std::move( bspNodes ); + bsp.bspLeafSurfaces = std::move( bspLeafSurfaces ); + bsp.bspLeafBrushes = std::move( bspLeafBrushes ); + bsp.bspBrushes = std::move( bspBrushes ); + bsp.bspBrushSides = std::move( bspBrushSides ); + bsp.bspLightBytes = std::move( bspLightBytes ); + bsp.bspGridPoints = std::move( bspGridPoints ); + bsp.bspVisBytes = std::move( bspVisBytes ); + bsp.bspDrawVerts = std::move( bspDrawVerts ); + bsp.bspDrawIndexes = std::move( bspDrawIndexes ); + bsp.bspDrawSurfaces = std::move( bspDrawSurfaces ); + bsp.bspFogs = std::move( bspFogs ); + + /* do some path mangling */ + strcpy( source, ExpandArg( fileName1 ) ); + path_set_extension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + + + /* reindex */ + { + for( auto&& model : bsp.bspModels ) + { + model.firstBSPSurface += bspDrawSurfaces.size(); + model.firstBSPBrush += bspBrushes.size(); + } + + for( auto&& side : bsp.bspBrushSides ){ + side.planeNum += bspPlanes.size(); + side.shaderNum += bspShaders.size(); + side.surfaceNum += bspDrawSurfaces.size(); + } + + for( auto&& brush : bsp.bspBrushes ) + { + brush.shaderNum += bspShaders.size(); + brush.firstSide += bspBrushSides.size(); + } + + for( auto&& fog : bsp.bspFogs ) + fog.brushNum += bspBrushes.size(); + + /* deduce max lm index, using bspLightBytes is insufficient for native external lightmaps */ + int maxLmIndex = -3; + for( const auto& surf : bspDrawSurfaces ) + for( auto index : surf.lightmapNum ) + value_maximize( maxLmIndex, index ); + for( auto&& surf : bsp.bspDrawSurfaces ) + { + surf.shaderNum += bspShaders.size(); + surf.fogNum += bspFogs.size(); + surf.firstVert += bspDrawVerts.size(); + surf.firstIndex += bspDrawIndexes.size(); + for( auto&& index : surf.lightmapNum ) + if( index >= 0 && maxLmIndex >= 0 ) + index += maxLmIndex + 1; + } + + ENSURE( bsp.entities[0].classname_is( "worldspawn" ) ); + for( auto&& e : bsp.entities ) + { + const char *model = e.valueForKey( "model" ); + if( model[0] == '*' ){ + e.setKeyValue( "model", StringOutputStream( 8 )( '*', atoi( model + 1 ) + bspModels.size() - 1 ) ); // -1 : minus world + } + } + /* make target/targetname names unique */ + if( fixnames ){ ///! assumes there are no dummy targetnames to fix in incoming bsp + const auto has_name = []( const std::vector& entities, const char *name ){ + for( auto&& e : entities ) + if( striEqual( name, e.valueForKey( "target" ) ) + || striEqual( name, e.valueForKey( "targetname" ) ) ) + return true; + return false; + }; + for( auto&& e : bsp.entities ) + { + if( const char *name; e.read_keyvalue( name, "target" ) ){ + if( has_name( entities, name ) ){ + StringOutputStream newName; + int id = 0; + do{ + newName( name, '_', id++ ); + } while( has_name( entities, newName ) + || has_name( bsp.entities, newName ) ); + + const CopiedString oldName = name; // backup it, original will change + for( auto&& e : bsp.entities ) + for( auto&& ep : e.epairs ) + if( ( striEqual( ep.key.c_str(), "target" ) || striEqual( ep.key.c_str(), "targetname" ) ) + && striEqual( ep.value.c_str(), oldName.c_str() ) ) + ep.value = newName; + } + } + } + } + } + + { + entities.insert( entities.cend(), bsp.entities.cbegin() + 1, bsp.entities.cend() ); // minus world + numBSPEntities = entities.size(); + bspModels.insert( bspModels.cend(), bsp.bspModels.cbegin() + 1, bsp.bspModels.cend() ); // minus world + bspShaders.insert( bspShaders.cend(), bsp.bspShaders.cbegin(), bsp.bspShaders.cend() ); + // bspLeafs + bspPlanes.insert( bspPlanes.cend(), bsp.bspPlanes.cbegin(), bsp.bspPlanes.cend() ); + // bspNodes + // bspLeafSurfaces + // bspLeafBrushes + bspBrushes.insert( bspBrushes.cend(), bsp.bspBrushes.cbegin(), bsp.bspBrushes.cend() ); + bspBrushSides.insert( bspBrushSides.cend(), bsp.bspBrushSides.cbegin(), bsp.bspBrushSides.cend() ); + bspLightBytes.insert( bspLightBytes.cend(), bsp.bspLightBytes.cbegin(), bsp.bspLightBytes.cend() ); + // bspGridPoints + // bspVisBytes + bspDrawVerts.insert( bspDrawVerts.cend(), bsp.bspDrawVerts.cbegin(), bsp.bspDrawVerts.cend() ); + bspDrawIndexes.insert( bspDrawIndexes.cend(), bsp.bspDrawIndexes.cbegin(), bsp.bspDrawIndexes.cend() ); + bspDrawSurfaces.insert( bspDrawSurfaces.cend(), bsp.bspDrawSurfaces.cbegin(), bsp.bspDrawSurfaces.cend() ); + bspFogs.insert( bspFogs.cend(), bsp.bspFogs.cbegin(), bsp.bspFogs.cend() ); + } + + if( addworld ){ + /* insert new world surfaces */ + const std::vector surfs( bspDrawSurfaces.cbegin() + bsp.bspModels[0].firstBSPSurface, + bspDrawSurfaces.cbegin() + bsp.bspModels[0].firstBSPSurface + bsp.bspModels[0].numBSPSurfaces ); + bspDrawSurfaces.insert( bspDrawSurfaces.cbegin() + bspModels[0].firstBSPSurface + bspModels[0].numBSPSurfaces, + surfs.cbegin(), surfs.cend() ); + // reindex + for( auto&& index : bspLeafSurfaces ) + if( index >= bspModels[0].firstBSPSurface + bspModels[0].numBSPSurfaces ) + index += surfs.size(); + for( auto&& side : bspBrushSides ) + if( side.surfaceNum >= bspModels[0].firstBSPSurface + bspModels[0].numBSPSurfaces ) + side.surfaceNum += surfs.size(); + for( auto&& model : bspModels ) + if( model.firstBSPSurface >= bspModels[0].firstBSPSurface + bspModels[0].numBSPSurfaces ) + model.firstBSPSurface += surfs.size(); + bspModels[0].numBSPSurfaces += surfs.size(); + /* insert new world brushes */ + const std::vector brushes( bspBrushes.cbegin() + bsp.bspModels[0].firstBSPBrush, + bspBrushes.cbegin() + bsp.bspModels[0].firstBSPBrush + bsp.bspModels[0].numBSPBrushes ); + bspBrushes.insert( bspBrushes.cbegin() + bspModels[0].firstBSPBrush + bspModels[0].numBSPBrushes, + brushes.cbegin(), brushes.cend() ); + // reindex + for( auto&& index : bspLeafBrushes ) + if( index >= bspModels[0].firstBSPBrush + bspModels[0].numBSPBrushes ) + index += brushes.size(); + for( auto&& fog : bspFogs ) + if( fog.brushNum >= bspModels[0].firstBSPBrush + bspModels[0].numBSPBrushes ) + fog.brushNum += brushes.size(); + for( auto&& model : bspModels ) + if( model.firstBSPBrush >= bspModels[0].firstBSPBrush + bspModels[0].numBSPBrushes ) + model.firstBSPBrush += brushes.size(); + bspModels[0].numBSPBrushes += brushes.size(); + /* reference surfaces */ + for( auto end = bspDrawSurfaces.cbegin() + bspModels[0].firstBSPSurface + bspModels[0].numBSPSurfaces, + surf = end - surfs.size(); surf != end; ++surf ){ + MinMax minmax; // cheap minmax test + if( surf->surfaceType == MST_BAD ){ + continue; + } + else if( surf->surfaceType == MST_PATCH ){ + minmax = { surf->lightmapVecs[0], surf->lightmapVecs[1] }; + } + else{ + for( int i = 0; i < surf->numIndexes; ++i ) + minmax.extend( bspDrawVerts[surf->firstVert + bspDrawIndexes[surf->firstIndex + i]].xyz ); + } + + for( auto&& leaf : bspLeafs ){ + if( leaf.minmax.test( minmax ) ){ + for( auto&& l : bspLeafs ) + if( &l != &leaf && l.firstBSPLeafSurface >= leaf.firstBSPLeafSurface ) + ++l.firstBSPLeafSurface; + + bspLeafSurfaces.insert( bspLeafSurfaces.cbegin() + leaf.firstBSPLeafSurface, int( std::distance( bspDrawSurfaces.cbegin(), surf ) ) ); + ++leaf.numBSPLeafSurfaces; + } + } + } + /* reference brushes */ + /* convert bsp planes to map planes */ + mapplanes.resize( bspPlanes.size() ); + for ( size_t i = 0; i < bspPlanes.size(); ++i ) + { + mapplanes[i].plane = bspPlanes[i]; + } + + for( auto end = bspBrushes.cbegin() + bspModels[0].firstBSPBrush + bspModels[0].numBSPBrushes, + brush = end - brushes.size(); brush != end; ++brush ){ + buildBrush.sides.clear(); + for( auto side = bspBrushSides.cbegin() + brush->firstSide, end = side + brush->numSides; side != end; ++side ){ + auto& s = buildBrush.sides.emplace_back(); + s.planenum = side->planeNum; + } + if( CreateBrushWindings( buildBrush ) ){ + // cheap minmax test + for( auto&& leaf : bspLeafs ){ + if( leaf.minmax.test( buildBrush.minmax ) ){ + for( auto&& l : bspLeafs ) + if( &l != &leaf && l.firstBSPLeafBrush >= leaf.firstBSPLeafBrush ) + ++l.firstBSPLeafBrush; + + bspLeafBrushes.insert( bspLeafBrushes.cbegin() + leaf.firstBSPLeafBrush, int( std::distance( bspBrushes.cbegin(), brush ) ) ); + ++leaf.numBSPLeafBrushes; + } + } + } + } + } + + + /* inject command line parameters */ + InjectCommandLine( "-mergebsp", argsToInject ); + + /* write the bsp */ + UnparseEntities(); + path_set_extension( source, "_merged.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; +} + + /* PseudoCompileBSP() a stripped down ProcessModels diff --git a/tools/quake3/q3map2/help.cpp b/tools/quake3/q3map2/help.cpp index c3319ab8..c4622d5b 100644 --- a/tools/quake3/q3map2/help.cpp +++ b/tools/quake3/q3map2/help.cpp @@ -433,6 +433,17 @@ void HelpJson() HelpOptions("BSP json export/import", 0, 80, options); } +void HelpMergeBsp() +{ + const std::vector options = { + {"-mergebsp [options] ", "Inject latter BSP to former. Tree and vis data of the main one are preserved."}, + {"-fixnames", "Make incoming BSP target/targetname names unique to not collide with existing names"}, + {"-world", "Also merge worldspawn model (brushes as if they were detail, no BSP tree is affected) (only merges entities by default)"}, + }; + + HelpOptions("BSP merge", 0, 80, options); +} + void HelpCommon() { const std::vector options = { @@ -486,6 +497,7 @@ void HelpMain(const char* arg) {"-pk3", "PK3 creation"}, {"-repack", "Maps repack creation"}, {"-json", "BSP json export/import"}, + {"-mergebsp", "BSP merge"}, }; void(*help_funcs[])() = { HelpBsp, @@ -504,6 +516,7 @@ void HelpMain(const char* arg) HelpPk3, HelpRepack, HelpJson, + HelpMergeBsp, }; if ( !strEmptyOrNull( arg ) ) diff --git a/tools/quake3/q3map2/main.cpp b/tools/quake3/q3map2/main.cpp index 73d1fdfc..cadb318e 100644 --- a/tools/quake3/q3map2/main.cpp +++ b/tools/quake3/q3map2/main.cpp @@ -225,6 +225,11 @@ int main( int argc, char **argv ){ r = ConvertJsonMain( args ); } + /* merge two bsps */ + else if ( args.takeFront( "-mergebsp" ) ) { + r = MergeBSPMain( args ); + } + /* div0: minimap */ else if ( args.takeFront( "-minimap" ) ) { r = MiniMapBSPMain( args ); diff --git a/tools/quake3/q3map2/q3map2.h b/tools/quake3/q3map2/q3map2.h index 1d70c2c7..70fe6455 100644 --- a/tools/quake3/q3map2/q3map2.h +++ b/tools/quake3/q3map2/q3map2.h @@ -1528,6 +1528,7 @@ int AnalyzeBSP( Args& args ); int BSPInfo( Args& args ); int ScaleBSPMain( Args& args ); int ShiftBSPMain( Args& args ); +int MergeBSPMain( Args& args ); int ConvertBSPMain( Args& args ); /* convert_map.c */