* -mergebsp [options] <mainBsp.bsp> <bspToinject.bsp>: 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)
This commit is contained in:
Garux 2021-10-13 22:50:43 +03:00
parent eceb2e3259
commit a719e012fe
5 changed files with 310 additions and 0 deletions

View File

@ -429,6 +429,15 @@ td.formatted_questions ol { margin-top: 0px; margin-bottom: 0px; }
</div>
<h2 id="BSP-merge">BSP merge<a href="#BSP-merge" class="wiki-anchor">&para;</a></h2>
<ul>
<li><strong><code>-mergebsp</code> ... mainBsp.bsp bspToinject.bsp:</strong> Inject latter BSP to former. Tree and vis data of the main one are preserved.</li>
<li><strong><code>-fixnames</code>:</strong> Make incoming BSP target/targetname names unique to not collide with existing names</li>
<li><strong><code>-world</code>:</strong> Also merge worldspawn model (brushes as if they were detail, no BSP tree is affected) (only merges entities by default)</li>
</ul>
</div>

View File

@ -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] <mainBsp> <bspToinject>\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<entity_t> entities;
std::vector<bspModel_t> bspModels;
std::vector<bspShader_t> bspShaders;
std::vector<bspLeaf_t> bspLeafs;
std::vector<bspPlane_t> bspPlanes;
std::vector<bspNode_t> bspNodes;
std::vector<int> bspLeafSurfaces;
std::vector<int> bspLeafBrushes;
std::vector<bspBrush_t> bspBrushes;
std::vector<bspBrushSide_t> bspBrushSides;
std::vector<byte> bspLightBytes;
std::vector<bspGridPoint_t> bspGridPoints;
std::vector<byte> bspVisBytes;
std::vector<bspDrawVert_t> bspDrawVerts;
std::vector<int> bspDrawIndexes;
std::vector<bspDrawSurface_t> bspDrawSurfaces;
std::vector<bspFog_t> 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<entity_t>& 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<bspDrawSurface_t> 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<bspBrush_t> 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

View File

@ -433,6 +433,17 @@ void HelpJson()
HelpOptions("BSP json export/import", 0, 80, options);
}
void HelpMergeBsp()
{
const std::vector<HelpOption> options = {
{"-mergebsp [options] <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)"},
};
HelpOptions("BSP merge", 0, 80, options);
}
void HelpCommon()
{
const std::vector<HelpOption> 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 ) )

View File

@ -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 );

View File

@ -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 */