You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
9.6 KiB
350 lines
9.6 KiB
// nav_merge.cpp |
|
// Save/merge for partial nav meshes |
|
// Copyright 2005 Turtle Rock Studios, Inc. |
|
|
|
#include "cbase.h" |
|
#include "fmtstr.h" |
|
#include "tier0/vprof.h" |
|
#include "utldict.h" |
|
|
|
#include "nav_mesh.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
void CNavArea::SaveToSelectedSet( KeyValues *areaKey ) const |
|
{ |
|
const char *placeName = TheNavMesh->PlaceToName( GetPlace() ); |
|
areaKey->SetString( "Place", (placeName)?placeName:"" ); |
|
|
|
areaKey->SetInt( "Attributes", GetAttributes() ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
void CNavArea::RestoreFromSelectedSet( KeyValues *areaKey ) |
|
{ |
|
SetPlace( TheNavMesh->NameToPlace( areaKey->GetString( "Place" ) ) ); |
|
|
|
SetAttributes( areaKey->GetInt( "Attributes" ) ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
class BuildSelectedSet |
|
{ |
|
public: |
|
BuildSelectedSet( KeyValues *kv ) |
|
{ |
|
m_kv = kv; |
|
m_areaCount = 0; |
|
} |
|
|
|
bool operator() ( CNavArea *area ) |
|
{ |
|
CFmtStrN<32> name( "%d", area->GetID() ); |
|
KeyValues *areaKey = m_kv->FindKey( name.Access(), true ); |
|
if ( areaKey ) |
|
{ |
|
++m_areaCount; |
|
|
|
WriteCorner( area, areaKey, NORTH_WEST, "NorthWest" ); |
|
WriteCorner( area, areaKey, NORTH_EAST, "NorthEast" ); |
|
WriteCorner( area, areaKey, SOUTH_WEST, "SouthWest" ); |
|
WriteCorner( area, areaKey, SOUTH_EAST, "SouthEast" ); |
|
|
|
WriteConnections( area, areaKey, NORTH, "North" ); |
|
WriteConnections( area, areaKey, SOUTH, "South" ); |
|
WriteConnections( area, areaKey, EAST, "East" ); |
|
WriteConnections( area, areaKey, WEST, "West" ); |
|
|
|
area->SaveToSelectedSet( areaKey ); |
|
} |
|
return true; |
|
} |
|
|
|
int Count( void ) const |
|
{ |
|
return m_areaCount; |
|
} |
|
|
|
private: |
|
void WriteCorner( CNavArea *area, KeyValues *areaKey, NavCornerType corner, const char *cornerName ) |
|
{ |
|
KeyValues *cornerKey = areaKey->FindKey( cornerName, true ); |
|
if ( cornerKey ) |
|
{ |
|
Vector pos = area->GetCorner( corner ); |
|
cornerKey->SetFloat( "x", pos.x ); |
|
cornerKey->SetFloat( "y", pos.y ); |
|
cornerKey->SetFloat( "z", pos.z ); |
|
} |
|
} |
|
|
|
void WriteConnections( CNavArea *area, KeyValues *areaKey, NavDirType dir, const char *dirName ) |
|
{ |
|
KeyValues *dirKey = areaKey->FindKey( dirName, true ); |
|
if ( dirKey ) |
|
{ |
|
for ( int i=0; i<area->GetAdjacentCount( dir ); ++i ) |
|
{ |
|
CNavArea *other = area->GetAdjacentArea( dir, i ); |
|
if ( other && TheNavMesh->IsInSelectedSet( other ) ) |
|
{ |
|
CFmtStrN<32> name( "%d", i ); |
|
dirKey->SetInt( name.Access(), other->GetID() ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
int m_areaCount; |
|
KeyValues *m_kv; |
|
}; |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
void CNavMesh::CommandNavSaveSelected( const CCommand &args ) |
|
{ |
|
KeyValues *data = new KeyValues( "Selected Nav Areas" ); |
|
data->SetInt( "version", 1 ); |
|
|
|
BuildSelectedSet setBuilder( data ); |
|
TheNavMesh->ForAllSelectedAreas( setBuilder ); |
|
|
|
if ( !setBuilder.Count() ) |
|
{ |
|
Msg( "Not saving empty selected set to disk.\n" ); |
|
data->deleteThis(); |
|
return; |
|
} |
|
|
|
char fname[32]; |
|
char path[MAX_PATH]; |
|
if ( args.ArgC() == 2 ) |
|
{ |
|
V_FileBase( args[0], fname, sizeof( fname ) ); |
|
} |
|
else |
|
{ |
|
V_strncpy( fname, STRING( gpGlobals->mapname ), sizeof( fname ) ); |
|
} |
|
|
|
int i; |
|
for ( i=0; i<1000; ++i ) |
|
{ |
|
V_snprintf( path, sizeof( path ), "maps/%s_selected_%4.4d.txt", fname, i ); |
|
if ( !filesystem->FileExists( path ) ) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
if ( i == 1000 ) |
|
{ |
|
Msg( "Unable to find a filename to save the selected set to disk.\n" ); |
|
data->deleteThis(); |
|
return; |
|
} |
|
|
|
if ( !data->SaveToFile( filesystem, path ) ) |
|
{ |
|
Msg( "Unable to save the selected set to disk.\n" ); |
|
} |
|
|
|
Msg( "Selected set saved to %s. Use 'nav_merge_mesh %s_selected_%4.4d' to merge it into another mesh.\n", path, fname, i ); |
|
data->deleteThis(); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
CON_COMMAND_F( nav_save_selected, "Writes the selected set to disk for merging into another mesh via nav_merge_mesh.", FCVAR_GAMEDLL | FCVAR_CHEAT ) |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
TheNavMesh->CommandNavSaveSelected( args ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
Vector ReadCorner( KeyValues *areaKey, const char *cornerName ) |
|
{ |
|
Vector pos( vec3_origin ); |
|
KeyValues *cornerKey = areaKey->FindKey( cornerName, false ); |
|
if ( cornerKey ) |
|
{ |
|
pos.x = cornerKey->GetFloat( "x" ); |
|
pos.y = cornerKey->GetFloat( "y" ); |
|
pos.z = cornerKey->GetFloat( "z" ); |
|
} |
|
|
|
return pos; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
void ReconnectMergedArea( CUtlDict< CNavArea *, int > &newAreas, KeyValues *areaKey, NavDirType dir, const char *dirName ) |
|
{ |
|
int index = newAreas.Find( areaKey->GetName() ); |
|
if ( index == newAreas.InvalidIndex() ) |
|
{ |
|
Assert( false ); |
|
return; |
|
} |
|
|
|
CNavArea *area = newAreas[index]; |
|
|
|
KeyValues *dirKey = areaKey->FindKey( dirName, true ); |
|
if ( dirKey ) |
|
{ |
|
KeyValues *connection = dirKey->GetFirstValue(); |
|
while ( connection ) |
|
{ |
|
const char *otherID = connection->GetString(); |
|
int otherIndex = newAreas.Find( otherID ); |
|
Assert( otherIndex != newAreas.InvalidIndex() ); |
|
if ( otherIndex != newAreas.InvalidIndex() ) |
|
{ |
|
CNavArea *other = newAreas[otherIndex]; |
|
|
|
area->ConnectTo( other, dir ); // only a 1-way connection. the other area will connect back to us. |
|
} |
|
|
|
connection = connection->GetNextValue(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
void CNavMesh::CommandNavMergeMesh( const CCommand &args ) |
|
{ |
|
if ( args.ArgC() != 2 ) |
|
{ |
|
Msg( "Usage: nav_merge_mesh filename\n" ); |
|
return; |
|
} |
|
|
|
char fname[64]; |
|
char path[MAX_PATH]; |
|
V_FileBase( args[1], fname, sizeof( fname ) ); |
|
V_snprintf( path, sizeof( path ), "maps/%s.txt", fname ); |
|
|
|
KeyValues *data = new KeyValues( "Nav Selected Set" ); |
|
if ( !data->LoadFromFile( filesystem, path ) ) |
|
{ |
|
Msg( "Unable to load %s.\n", path ); |
|
} |
|
else |
|
{ |
|
// Loaded the data - plug it into the existing mesh! |
|
|
|
// First add the areas, and put them in the correct places. We can save off the new area ID |
|
// at the same time. |
|
CUtlDict< CNavArea *, int > newAreas; |
|
CUtlVector< CNavArea * > areaVector; |
|
KeyValues *areaKey = data->GetFirstSubKey(); |
|
while ( areaKey ) |
|
{ |
|
Vector northWest = ReadCorner( areaKey, "NorthWest" ); |
|
Vector northEast = ReadCorner( areaKey, "NorthEast" ); |
|
Vector southWest = ReadCorner( areaKey, "SouthWest" ); |
|
Vector southEast = ReadCorner( areaKey, "SouthEast" ); |
|
|
|
CNavArea *newArea = TheNavMesh->CreateArea(); |
|
if (newArea == NULL) |
|
{ |
|
Warning( "nav_merge_mesh: Out of memory\n" ); |
|
return; |
|
} |
|
|
|
newArea->Build( northWest, northEast, southEast, southWest ); |
|
TheNavAreas.AddToTail( newArea ); |
|
TheNavMesh->AddNavArea( newArea ); |
|
areaVector.AddToTail( newArea ); |
|
|
|
// save the new ID for connections |
|
int index = newAreas.Find( areaKey->GetName() ); |
|
Assert( index == newAreas.InvalidIndex() ); |
|
if ( index == newAreas.InvalidIndex() ) |
|
{ |
|
newAreas.Insert( areaKey->GetName(), newArea ); |
|
} |
|
|
|
// Restore additional data |
|
newArea->RestoreFromSelectedSet( areaKey ); |
|
|
|
areaKey = areaKey->GetNextKey(); |
|
} |
|
|
|
// Go back and reconnect the new areas to each other |
|
areaKey = data->GetFirstSubKey(); |
|
while ( areaKey ) |
|
{ |
|
ReconnectMergedArea( newAreas, areaKey, NORTH, "North" ); |
|
ReconnectMergedArea( newAreas, areaKey, SOUTH, "South" ); |
|
ReconnectMergedArea( newAreas, areaKey, EAST, "East" ); |
|
ReconnectMergedArea( newAreas, areaKey, WEST, "West" ); |
|
|
|
areaKey = areaKey->GetNextKey(); |
|
} |
|
|
|
// Connect selected areas with pre-existing areas |
|
StitchAreaSet( &areaVector ); |
|
} |
|
|
|
data->deleteThis(); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
int NavMeshMergeAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) |
|
{ |
|
char *commandName = "nav_merge_mesh"; |
|
int numMatches = 0; |
|
partial += Q_strlen( commandName ) + 1; |
|
int partialLength = Q_strlen( partial ); |
|
|
|
FileFindHandle_t findHandle; |
|
char txtFilenameNoExtension[ MAX_PATH ]; |
|
const char *txtFilename = filesystem->FindFirstEx( "maps/*_selected_*.txt", "MOD", &findHandle ); |
|
while ( txtFilename ) |
|
{ |
|
Q_FileBase( txtFilename, txtFilenameNoExtension, sizeof( txtFilenameNoExtension ) ); |
|
if ( !Q_strnicmp( txtFilenameNoExtension, partial, partialLength ) && V_stristr( txtFilenameNoExtension, "_selected_" ) ) |
|
{ |
|
// Add the place name to the autocomplete array |
|
Q_snprintf( commands[ numMatches++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", commandName, txtFilenameNoExtension ); |
|
|
|
// Make sure we don't try to return too many place names |
|
if ( numMatches == COMMAND_COMPLETION_MAXITEMS ) |
|
return numMatches; |
|
} |
|
|
|
txtFilename = filesystem->FindNext( findHandle ); |
|
} |
|
filesystem->FindClose( findHandle ); |
|
|
|
return numMatches; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
CON_COMMAND_F_COMPLETION( nav_merge_mesh, "Merges a saved selected set into the current mesh.", FCVAR_GAMEDLL | FCVAR_CHEAT, NavMeshMergeAutocomplete ) |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
TheNavMesh->CommandNavMergeMesh( args ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
|