2013-08-17 15:46:48 +03:00
/*
* CZonePlacer . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2023-04-10 09:02:58 +02:00
# include <stack>
2014-05-24 13:42:06 +03:00
# include "../CRandomGenerator.h"
2013-08-17 15:46:48 +03:00
# include "CZonePlacer.h"
2023-01-09 01:17:37 +02:00
# include "../TerrainHandler.h"
2015-12-02 21:05:10 +02:00
# include "../mapping/CMap.h"
2022-05-31 11:25:39 +02:00
# include "../mapping/CMapEditManager.h"
2022-12-14 02:37:11 +02:00
# include "CMapGenOptions.h"
2022-08-09 07:54:32 +02:00
# include "RmgMap.h"
# include "Zone.h"
# include "Functions.h"
2013-08-17 15:46:48 +03:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2014-05-24 13:42:06 +03:00
class CRandomGenerator ;
2022-08-09 07:54:32 +02:00
CZonePlacer : : CZonePlacer ( RmgMap & map )
2023-04-17 16:37:24 +02:00
: width ( 0 ) , height ( 0 ) , scaleX ( 0 ) , scaleY ( 0 ) , mapSize ( 0 ) ,
2023-04-20 16:13:30 +02:00
gravityConstant ( 1e-3 f ) ,
2023-04-17 16:37:24 +02:00
stiffnessConstant ( 3e-3 f ) ,
stifness ( 0 ) ,
2023-04-20 16:13:30 +02:00
stiffnessIncreaseFactor ( 1.03f ) ,
2023-04-20 12:44:32 +02:00
bestTotalDistance ( 1e10 ) ,
bestTotalOverlap ( 1e10 ) ,
2022-08-09 07:54:32 +02:00
map ( map )
2013-08-17 15:46:48 +03:00
{
}
2023-02-11 18:05:02 +02:00
int3 CZonePlacer : : cords ( const float3 & f ) const
2013-08-17 15:46:48 +03:00
{
2023-05-19 20:30:15 +02:00
return int3 ( static_cast < si32 > ( std : : max ( 0.f , ( f . x * map . width ( ) ) - 1 ) ) , static_cast < si32 > ( std : : max ( 0.f , ( f . y * map . height ( ) - 1 ) ) ) , f . z ) ;
2014-05-24 13:42:06 +03:00
}
2016-07-12 21:23:45 +02:00
float CZonePlacer : : getDistance ( float distance ) const
{
2020-10-01 10:38:06 +02:00
return ( distance ? distance * distance : 1e-6 f ) ;
2016-07-12 21:23:45 +02:00
}
2023-04-10 09:02:58 +02:00
void CZonePlacer : : findPathsBetweenZones ( )
{
auto zones = map . getZones ( ) ;
std : : set < std : : shared_ptr < Zone > > zonesToCheck ;
2023-04-16 20:24:42 +02:00
// Iterate through each pair of nodes in the graph
2023-04-10 09:02:58 +02:00
2023-04-16 20:24:42 +02:00
for ( const auto & zone : zones )
2023-04-10 09:02:58 +02:00
{
2023-04-16 20:24:42 +02:00
int start = zone . first ;
distancesBetweenZones [ start ] [ start ] = 0 ; // Distance from a node to itself is 0
std : : queue < int > q ;
std : : map < int , bool > visited ;
visited [ start ] = true ;
q . push ( start ) ;
2023-04-10 09:02:58 +02:00
2023-04-16 20:24:42 +02:00
// Perform Breadth-First Search from the starting node
while ( ! q . empty ( ) )
2023-04-10 09:02:58 +02:00
{
2023-04-16 20:24:42 +02:00
int current = q . front ( ) ;
q . pop ( ) ;
const auto & currentZone = zones . at ( current ) ;
2023-06-17 19:09:38 +02:00
const auto & connectedZoneIds = currentZone - > getConnections ( ) ;
2023-04-16 20:24:42 +02:00
2023-06-17 19:09:38 +02:00
for ( auto & connection : connectedZoneIds )
2023-04-10 09:02:58 +02:00
{
2023-07-07 16:32:17 +02:00
if ( connection . getConnectionType ( ) = = rmg : : EConnectionType : : REPULSIVE )
2023-06-17 19:09:38 +02:00
{
2023-06-23 21:43:12 +02:00
//Do not consider virtual connections for graph distance
2023-06-17 19:09:38 +02:00
continue ;
}
auto neighbor = connection . getOtherZoneId ( current ) ;
2023-04-16 20:24:42 +02:00
if ( ! visited [ neighbor ] )
2023-04-10 09:02:58 +02:00
{
2023-04-16 20:24:42 +02:00
visited [ neighbor ] = true ;
q . push ( neighbor ) ;
distancesBetweenZones [ start ] [ neighbor ] = distancesBetweenZones [ start ] [ current ] + 1 ;
2023-04-10 09:02:58 +02:00
}
}
}
}
}
void CZonePlacer : : placeOnGrid ( CRandomGenerator * rand )
{
auto zones = map . getZones ( ) ;
assert ( zones . size ( ) ) ;
//Make sure there are at least as many grid fields as the number of zones
size_t gridSize = std : : ceil ( std : : sqrt ( zones . size ( ) ) ) ;
typedef boost : : multi_array < std : : shared_ptr < Zone > , 2 > GridType ;
GridType grid ( boost : : extents [ gridSize ] [ gridSize ] ) ;
TZoneVector zonesVector ( zones . begin ( ) , zones . end ( ) ) ;
//Place first zone
auto firstZone = zonesVector [ 0 ] . second ;
size_t x = 0 , y = 0 ;
auto getRandomEdge = [ rand , gridSize ] ( size_t & x , size_t & y )
{
switch ( rand - > nextInt ( ) % 4 )
{
case 0 :
x = 0 ;
y = gridSize / 2 ;
break ;
case 1 :
x = gridSize - 1 ;
y = gridSize / 2 ;
break ;
case 2 :
x = gridSize / 2 ;
y = 0 ;
break ;
case 3 :
x = gridSize / 2 ;
y = gridSize - 1 ;
break ;
}
} ;
switch ( firstZone - > getType ( ) )
{
case ETemplateZoneType : : PLAYER_START :
case ETemplateZoneType : : CPU_START :
2023-06-17 19:09:38 +02:00
if ( firstZone - > getConnectedZoneIds ( ) . size ( ) > 2 )
2023-04-10 09:02:58 +02:00
{
getRandomEdge ( x , y ) ;
}
else
{
//Random corner
if ( rand - > nextInt ( ) % 2 )
{
x = 0 ;
}
else
{
x = gridSize - 1 ;
}
if ( rand - > nextInt ( ) % 2 )
{
y = 0 ;
}
else
{
y = gridSize - 1 ;
}
}
break ;
case ETemplateZoneType : : TREASURE :
2023-04-19 08:45:23 +02:00
if ( gridSize & 1 ) //odd
2023-04-10 09:02:58 +02:00
{
x = y = ( gridSize / 2 ) ;
}
else
{
//One of 4 squares in the middle
x = ( gridSize / 2 ) - 1 + rand - > nextInt ( ) % 2 ;
y = ( gridSize / 2 ) - 1 + rand - > nextInt ( ) % 2 ;
}
break ;
case ETemplateZoneType : : JUNCTION :
getRandomEdge ( x , y ) ;
break ;
}
grid [ x ] [ y ] = firstZone ;
//Ignore z placement for simplicity
for ( size_t i = 1 ; i < zones . size ( ) ; i + + )
{
auto zone = zonesVector [ i ] . second ;
2023-06-17 19:09:38 +02:00
auto connectedZoneIds = zone - > getConnectedZoneIds ( ) ;
2023-04-10 09:02:58 +02:00
2023-04-10 09:43:58 +02:00
float maxDistance = - 1000.0 ;
2023-04-10 09:02:58 +02:00
int3 mostDistantPlace ;
//Iterate over free positions
for ( size_t freeX = 0 ; freeX < gridSize ; + + freeX )
{
for ( size_t freeY = 0 ; freeY < gridSize ; + + freeY )
{
if ( ! grid [ freeX ] [ freeY ] )
{
//There is free space left here
int3 potentialPos ( freeX , freeY , 0 ) ;
//Compute distance to every existing zone
2023-04-10 09:43:58 +02:00
float distance = 0 ;
2023-04-10 09:02:58 +02:00
for ( size_t existingX = 0 ; existingX < gridSize ; + + existingX )
{
for ( size_t existingY = 0 ; existingY < gridSize ; + + existingY )
{
auto existingZone = grid [ existingX ] [ existingY ] ;
2023-04-10 09:43:58 +02:00
if ( existingZone )
2023-04-10 09:02:58 +02:00
{
//There is already zone here
2023-04-10 09:43:58 +02:00
float localDistance = 0.0f ;
2023-04-10 09:02:58 +02:00
2023-04-10 09:43:58 +02:00
auto graphDistance = distancesBetweenZones [ zone - > getId ( ) ] [ existingZone - > getId ( ) ] ;
if ( graphDistance > 1 )
2023-04-10 09:02:58 +02:00
{
//No direct connection
2023-04-10 09:43:58 +02:00
localDistance = potentialPos . dist2d ( int3 ( existingX , existingY , 0 ) ) * graphDistance ;
2023-04-10 09:02:58 +02:00
}
else
{
2023-04-10 09:43:58 +02:00
//Has direct connection - place as close as possible
2023-04-10 11:11:35 +02:00
localDistance = - potentialPos . dist2d ( int3 ( existingX , existingY , 0 ) ) ;
2023-04-10 09:43:58 +02:00
}
2023-06-23 21:43:12 +02:00
localDistance * = scaleForceBetweenZones ( zone , existingZone ) ;
2023-04-10 09:02:58 +02:00
2023-04-10 09:43:58 +02:00
distance + = localDistance ;
2023-04-10 09:02:58 +02:00
}
}
}
2023-04-10 10:00:24 +02:00
if ( distance > maxDistance )
{
maxDistance = distance ;
mostDistantPlace = potentialPos ;
}
2023-04-10 09:02:58 +02:00
}
}
}
//Place in a free slot
grid [ mostDistantPlace . x ] [ mostDistantPlace . y ] = zone ;
}
//TODO: toggle with a flag
logGlobal - > info ( " Initial zone grid: " ) ;
for ( size_t x = 0 ; x < gridSize ; + + x )
{
std : : string s ;
for ( size_t y = 0 ; y < gridSize ; + + y )
{
if ( grid [ x ] [ y ] )
{
s + = ( boost : : format ( " %3d " ) % grid [ x ] [ y ] - > getId ( ) ) . str ( ) ;
}
else
{
s + = " -- " ;
}
}
logGlobal - > info ( s ) ;
}
//Set initial position for zones - random position in square centered around (x, y)
for ( size_t x = 0 ; x < gridSize ; + + x )
{
for ( size_t y = 0 ; y < gridSize ; + + y )
{
auto zone = grid [ x ] [ y ] ;
if ( zone )
{
2023-04-20 12:24:57 +02:00
//i.e. for grid size 5 we get range (0.25 - 4.75)
2023-04-17 15:09:08 +02:00
auto targetX = rand - > nextDouble ( x + 0.25f , x + 0.75f ) ;
vstd : : abetween ( targetX , 0.5 , gridSize - 0.5 ) ;
auto targetY = rand - > nextDouble ( y + 0.25f , y + 0.75f ) ;
vstd : : abetween ( targetY , 0.5 , gridSize - 0.5 ) ;
2023-04-10 09:02:58 +02:00
zone - > setCenter ( float3 ( targetX / gridSize , targetY / gridSize , zone - > getPos ( ) . z ) ) ;
}
}
}
}
2023-06-23 21:43:12 +02:00
float CZonePlacer : : scaleForceBetweenZones ( const std : : shared_ptr < Zone > zoneA , const std : : shared_ptr < Zone > zoneB ) const
{
if ( zoneA - > getOwner ( ) & & zoneB - > getOwner ( ) ) //Players participate in game
{
int firstPlayer = zoneA - > getOwner ( ) . value ( ) ;
int secondPlayer = zoneB - > getOwner ( ) . value ( ) ;
//Players with lower indexes (especially 1 and 2) will be placed further apart
return ( 1.0f + ( 2.0f / ( firstPlayer * secondPlayer ) ) ) ;
}
else
{
return 1 ;
}
}
2022-05-28 15:03:50 +02:00
void CZonePlacer : : placeZones ( CRandomGenerator * rand )
2013-08-17 15:46:48 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > info ( " Starting zone placement " ) ;
2014-05-24 13:42:06 +03:00
2022-08-09 07:54:32 +02:00
width = map . getMapGenOptions ( ) . getWidth ( ) ;
height = map . getMapGenOptions ( ) . getHeight ( ) ;
2014-05-24 13:42:06 +03:00
2022-08-09 07:54:32 +02:00
auto zones = map . getZones ( ) ;
vstd : : erase_if ( zones , [ ] ( const std : : pair < TRmgTemplateZoneId , std : : shared_ptr < Zone > > & pr )
{
return pr . second - > getType ( ) = = ETemplateZoneType : : WATER ;
} ) ;
bool underground = map . getMapGenOptions ( ) . getHasTwoLevels ( ) ;
2014-05-24 13:42:06 +03:00
2023-04-10 09:02:58 +02:00
findPathsBetweenZones ( ) ;
placeOnGrid ( rand ) ;
2014-05-24 23:10:46 +03:00
/*
2023-04-17 14:56:35 +02:00
Fruchterman - Reingold algorithm
2014-05-24 23:10:46 +03:00
2023-04-17 14:56:35 +02:00
Let ' s assume we try to fit N circular zones with radius = size on a map
Connected zones attract , intersecting zones and map boundaries push back
2014-05-24 23:10:46 +03:00
*/
2014-07-06 11:43:30 +03:00
2016-07-13 07:54:52 +02:00
TZoneVector zonesVector ( zones . begin ( ) , zones . end ( ) ) ;
assert ( zonesVector . size ( ) ) ;
2014-12-23 14:49:07 +02:00
2016-07-13 07:54:52 +02:00
RandomGeneratorUtil : : randomShuffle ( zonesVector , * rand ) ;
2014-07-03 13:28:51 +03:00
2016-07-13 07:54:52 +02:00
//0. set zone sizes and surface / underground level
prepareZones ( zones , zonesVector , underground , rand ) ;
2014-05-24 13:42:06 +03:00
2022-08-09 07:54:32 +02:00
std : : map < std : : shared_ptr < Zone > , float3 > bestSolution ;
2014-12-23 00:35:19 +02:00
2016-07-12 21:23:45 +02:00
TForceVector forces ;
TForceVector totalForces ; // both attraction and pushback, overcomplicated?
TDistanceVector distances ;
TDistanceVector overlaps ;
2016-07-11 15:44:24 +02:00
2023-06-14 14:07:31 +02:00
auto evaluateSolution = [ this , zones , & distances , & overlaps , & bestSolution ] ( ) - > bool
{
bool improvement = false ;
float totalDistance = 0 ;
float totalOverlap = 0 ;
for ( const auto & zone : distances ) //find most misplaced zone
{
totalDistance + = zone . second ;
float overlap = overlaps [ zone . first ] ;
totalOverlap + = overlap ;
}
//check fitness function
if ( ( totalDistance + 1 ) * ( totalOverlap + 1 ) < ( bestTotalDistance + 1 ) * ( bestTotalOverlap + 1 ) )
{
//multiplication is better for auto-scaling, but stops working if one factor is 0
improvement = true ;
}
//Save best solution
if ( improvement )
{
bestTotalDistance = totalDistance ;
bestTotalOverlap = totalOverlap ;
for ( const auto & zone : zones )
bestSolution [ zone . second ] = zone . second - > getCenter ( ) ;
}
logGlobal - > trace ( " Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s " , totalDistance , totalOverlap , improvement ) ;
return improvement ;
} ;
2023-04-17 16:37:24 +02:00
//Start with low stiffness. Bigger graphs need more time and more flexibility
2023-06-14 14:07:31 +02:00
for ( stifness = stiffnessConstant / zones . size ( ) ; stifness < = stiffnessConstant ; )
2014-05-24 13:42:06 +03:00
{
2016-07-10 18:16:32 +02:00
//1. attract connected zones
2016-07-12 21:23:45 +02:00
attractConnectedZones ( zones , forces , distances ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : forces )
2016-07-10 18:16:32 +02:00
{
2016-07-12 21:23:45 +02:00
zone . first - > setCenter ( zone . first - > getCenter ( ) + zone . second ) ;
totalForces [ zone . first ] = zone . second ; //override
2016-07-10 18:16:32 +02:00
}
2016-07-12 21:23:45 +02:00
//2. separate overlapping zones
separateOverlappingZones ( zones , forces , overlaps ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : forces )
2014-05-24 23:10:46 +03:00
{
2014-07-04 19:50:29 +03:00
zone . first - > setCenter ( zone . first - > getCenter ( ) + zone . second ) ;
2016-07-12 21:23:45 +02:00
totalForces [ zone . first ] + = zone . second ; //accumulate
2014-05-24 19:39:58 +03:00
}
2014-10-31 13:58:55 +02:00
2023-06-14 14:07:31 +02:00
bool improved = evaluateSolution ( ) ;
2016-07-12 21:23:45 +02:00
2023-06-14 14:07:31 +02:00
if ( ! improved )
2016-07-12 21:23:45 +02:00
{
2023-06-14 14:07:31 +02:00
//3. now perform drastic movement of zone that is completely not linked
//TODO: Don't do this is fitness was improved
moveOneZone ( zones , totalForces , distances , overlaps ) ;
2016-07-12 21:23:45 +02:00
2023-06-14 14:07:31 +02:00
improved | = evaluateSolution ( ) ; ;
2017-07-19 02:42:26 +02:00
}
2016-07-12 21:23:45 +02:00
2023-06-14 14:07:31 +02:00
if ( ! improved )
2016-07-12 21:23:45 +02:00
{
2023-06-14 14:07:31 +02:00
//Only cool down if we didn't see any improvement
stifness * = stiffnessIncreaseFactor ;
2016-07-12 21:23:45 +02:00
}
2023-06-14 14:07:31 +02:00
2014-05-24 13:42:06 +03:00
}
2014-12-23 00:35:19 +02:00
2017-08-10 19:17:10 +02:00
logGlobal - > trace ( " Best fitness reached: total distance %2.4f, total overlap %2.4f " , bestTotalDistance , bestTotalOverlap ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones ) //finalize zone positions
2014-05-24 13:42:06 +03:00
{
2014-12-22 22:33:37 +02:00
zone . second - > setPos ( cords ( bestSolution [ zone . second ] ) ) ;
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Placed zone %d at relative position %s and coordinates %s " , zone . first , zone . second - > getCenter ( ) . toString ( ) , zone . second - > getPos ( ) . toString ( ) ) ;
2014-05-24 13:42:06 +03:00
}
2013-08-17 15:46:48 +03:00
}
2014-05-24 15:06:08 +03:00
2016-07-13 07:54:52 +02:00
void CZonePlacer : : prepareZones ( TZoneMap & zones , TZoneVector & zonesVector , const bool underground , CRandomGenerator * rand )
2016-11-27 21:37:41 +02:00
{
2016-07-13 07:54:52 +02:00
std : : vector < float > totalSize = { 0 , 0 } ; //make sure that sum of zone sizes on surface and uderground match size of the map
int zonesOnLevel [ 2 ] = { 0 , 0 } ;
//even distribution for surface / underground zones. Surface zones always have priority.
TZoneVector zonesToPlace ;
std : : map < TRmgTemplateZoneId , int > levels ;
//first pass - determine fixed surface for zones
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zonesVector )
2016-11-27 21:37:41 +02:00
{
2016-08-06 12:16:13 +02:00
if ( ! underground ) //this step is ignored
zonesToPlace . push_back ( zone ) ;
else //place players depending on their factions
2016-07-13 07:54:52 +02:00
{
2023-04-16 19:42:56 +02:00
if ( std : : optional < int > owner = zone . second - > getOwner ( ) )
2016-08-06 10:04:03 +02:00
{
2016-08-06 12:16:13 +02:00
auto player = PlayerColor ( * owner - 1 ) ;
2022-08-09 07:54:32 +02:00
auto playerSettings = map . getMapGenOptions ( ) . getPlayersSettings ( ) ;
2023-08-18 12:38:19 +02:00
si32 faction = FactionID : : RANDOM ;
2016-08-06 12:16:13 +02:00
if ( vstd : : contains ( playerSettings , player ) )
faction = playerSettings [ player ] . getStartingTown ( ) ;
else
2017-08-10 18:39:27 +02:00
logGlobal - > error ( " Can't find info for player %d (starting zone) " , player . getNum ( ) ) ;
2016-08-06 12:16:13 +02:00
2023-08-18 12:38:19 +02:00
if ( faction = = FactionID : : RANDOM ) //TODO: check this after a town has already been randomized
2016-08-06 10:04:03 +02:00
zonesToPlace . push_back ( zone ) ;
2016-08-06 12:16:13 +02:00
else
{
2022-06-20 16:39:50 +02:00
auto & tt = ( * VLC - > townh ) [ faction ] - > nativeTerrain ;
2023-01-10 20:09:09 +02:00
if ( tt = = ETerrainId : : NONE )
2016-08-06 12:16:13 +02:00
{
//any / random
zonesToPlace . push_back ( zone ) ;
2022-06-20 16:39:50 +02:00
}
else
{
2022-12-20 16:14:06 +02:00
const auto & terrainType = VLC - > terrainTypeHandler - > getById ( tt ) ;
if ( terrainType - > isUnderground ( ) & & ! terrainType - > isSurface ( ) )
2022-06-20 16:39:50 +02:00
{
2022-09-22 18:23:31 +02:00
//underground only
2022-06-20 16:39:50 +02:00
zonesOnLevel [ 1 ] + + ;
levels [ zone . first ] = 1 ;
}
else
{
//surface
zonesOnLevel [ 0 ] + + ;
levels [ zone . first ] = 0 ;
}
2016-08-06 12:16:13 +02:00
}
2016-08-06 10:04:03 +02:00
}
}
2016-08-06 12:16:13 +02:00
else //no starting zone or no underground altogether
{
zonesToPlace . push_back ( zone ) ;
}
2016-07-13 07:54:52 +02:00
}
}
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zonesToPlace )
2016-07-13 07:54:52 +02:00
{
if ( underground ) //only then consider underground zones
{
int level = 0 ;
if ( zonesOnLevel [ 1 ] < zonesOnLevel [ 0 ] ) //only if there are less underground zones
level = 1 ;
else
level = 0 ;
levels [ zone . first ] = level ;
zonesOnLevel [ level ] + + ;
}
else
levels [ zone . first ] = 0 ;
}
2023-04-17 14:56:35 +02:00
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zonesVector )
2016-07-13 07:54:52 +02:00
{
int level = levels [ zone . first ] ;
totalSize [ level ] + = ( zone . second - > getSize ( ) * zone . second - > getSize ( ) ) ;
2023-04-17 14:56:35 +02:00
float3 center = zone . second - > getCenter ( ) ;
center . z = level ;
zone . second - > setCenter ( center ) ;
2016-07-13 07:54:52 +02:00
}
/*
prescale zones
formula : sum ( ( prescaler * n ) ^ 2 ) * pi = WH
prescaler = sqrt ( ( WH ) / ( sum ( n ^ 2 ) * pi ) )
*/
std : : vector < float > prescaler = { 0 , 0 } ;
for ( int i = 0 ; i < 2 ; i + + )
2023-02-11 18:05:02 +02:00
prescaler [ i ] = std : : sqrt ( ( width * height ) / ( totalSize [ i ] * 3.14f ) ) ;
2020-10-01 10:38:06 +02:00
mapSize = static_cast < float > ( sqrt ( width * height ) ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2016-07-13 07:54:52 +02:00
{
2023-02-11 18:05:02 +02:00
zone . second - > setSize ( static_cast < int > ( zone . second - > getSize ( ) * prescaler [ zone . second - > getCenter ( ) . z ] ) ) ;
2016-07-13 07:54:52 +02:00
}
}
2023-02-11 18:05:02 +02:00
void CZonePlacer : : attractConnectedZones ( TZoneMap & zones , TForceVector & forces , TDistanceVector & distances ) const
2016-07-12 21:23:45 +02:00
{
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2016-07-12 21:23:45 +02:00
{
float3 forceVector ( 0 , 0 , 0 ) ;
float3 pos = zone . second - > getCenter ( ) ;
float totalDistance = 0 ;
2023-06-17 19:09:38 +02:00
for ( const auto & connection : zone . second - > getConnections ( ) )
2016-07-12 21:23:45 +02:00
{
2023-07-07 16:32:17 +02:00
if ( connection . getConnectionType ( ) = = rmg : : EConnectionType : : REPULSIVE )
2023-06-17 19:09:38 +02:00
{
continue ;
}
auto otherZone = zones [ connection . getOtherZoneId ( zone . second - > getId ( ) ) ] ;
2016-07-12 21:23:45 +02:00
float3 otherZoneCenter = otherZone - > getCenter ( ) ;
2023-02-11 18:05:02 +02:00
auto distance = static_cast < float > ( pos . dist2d ( otherZoneCenter ) ) ;
2023-04-17 14:56:35 +02:00
2023-06-23 21:43:12 +02:00
forceVector + = ( otherZoneCenter - pos ) * distance * gravityConstant * scaleForceBetweenZones ( zone . second , otherZone ) ; //positive value
2023-04-17 14:56:35 +02:00
//Attract zone centers always
2016-07-12 21:23:45 +02:00
float minDistance = 0 ;
if ( pos . z ! = otherZoneCenter . z )
minDistance = 0 ; //zones on different levels can overlap completely
else
minDistance = ( zone . second - > getSize ( ) + otherZone - > getSize ( ) ) / mapSize ; //scale down to (0,1) coordinates
if ( distance > minDistance )
totalDistance + = ( distance - minDistance ) ;
}
distances [ zone . second ] = totalDistance ;
forceVector . z = 0 ; //operator - doesn't preserve z coordinate :/
forces [ zone . second ] = forceVector ;
}
}
void CZonePlacer : : separateOverlappingZones ( TZoneMap & zones , TForceVector & forces , TDistanceVector & overlaps )
{
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2016-07-12 21:23:45 +02:00
{
float3 forceVector ( 0 , 0 , 0 ) ;
float3 pos = zone . second - > getCenter ( ) ;
float overlap = 0 ;
2020-10-01 10:38:06 +02:00
//separate overlapping zones
2023-02-11 18:05:02 +02:00
for ( const auto & otherZone : zones )
2016-07-12 21:23:45 +02:00
{
float3 otherZoneCenter = otherZone . second - > getCenter ( ) ;
//zones on different levels don't push away
if ( zone = = otherZone | | pos . z ! = otherZoneCenter . z )
continue ;
2023-02-11 18:05:02 +02:00
auto distance = static_cast < float > ( pos . dist2d ( otherZoneCenter ) ) ;
2016-07-12 21:23:45 +02:00
float minDistance = ( zone . second - > getSize ( ) + otherZone . second - > getSize ( ) ) / mapSize ;
if ( distance < minDistance )
{
2023-04-17 16:37:24 +02:00
float3 localForce = ( ( ( otherZoneCenter - pos ) * ( minDistance / ( distance ? distance : 1e-3 f ) ) ) / getDistance ( distance ) ) * stifness ;
2023-04-16 21:12:21 +02:00
//negative value
2023-06-23 21:43:12 +02:00
localForce * = scaleForceBetweenZones ( zone . second , otherZone . second ) ;
2023-04-16 21:12:21 +02:00
forceVector - = localForce * ( distancesBetweenZones [ zone . second - > getId ( ) ] [ otherZone . second - > getId ( ) ] / 2.0f ) ;
2016-07-12 21:23:45 +02:00
overlap + = ( minDistance - distance ) ; //overlapping of small zones hurts us more
}
}
//move zones away from boundaries
//do not scale boundary distance - zones tend to get squashed
float size = zone . second - > getSize ( ) / mapSize ;
auto pushAwayFromBoundary = [ & forceVector , pos , size , & overlap , this ] ( float x , float y )
{
float3 boundary = float3 ( x , y , pos . z ) ;
2023-02-11 18:05:02 +02:00
auto distance = static_cast < float > ( pos . dist2d ( boundary ) ) ;
2016-07-12 21:23:45 +02:00
overlap + = std : : max < float > ( 0 , distance - size ) ; //check if we're closer to map boundary than value of zone size
2023-04-17 16:37:24 +02:00
forceVector - = ( boundary - pos ) * ( size - distance ) / this - > getDistance ( distance ) * this - > stifness ; //negative value
2016-07-12 21:23:45 +02:00
} ;
if ( pos . x < size )
{
pushAwayFromBoundary ( 0 , pos . y ) ;
}
if ( pos . x > 1 - size )
{
pushAwayFromBoundary ( 1 , pos . y ) ;
}
if ( pos . y < size )
{
pushAwayFromBoundary ( pos . x , 0 ) ;
}
if ( pos . y > 1 - size )
{
pushAwayFromBoundary ( pos . x , 1 ) ;
}
2023-06-17 19:09:38 +02:00
//Always move repulsive zones away, no matter their distance
//TODO: Consider z plane?
for ( auto & connection : zone . second - > getConnections ( ) )
{
2023-07-07 16:32:17 +02:00
if ( connection . getConnectionType ( ) = = rmg : : EConnectionType : : REPULSIVE )
2023-06-17 19:09:38 +02:00
{
auto & otherZone = zones [ connection . getOtherZoneId ( zone . second - > getId ( ) ) ] ;
float3 otherZoneCenter = otherZone - > getCenter ( ) ;
//TODO: Roll into lambda?
auto distance = static_cast < float > ( pos . dist2d ( otherZoneCenter ) ) ;
float minDistance = ( zone . second - > getSize ( ) + otherZone - > getSize ( ) ) / mapSize ;
float3 localForce = ( ( ( otherZoneCenter - pos ) * ( minDistance / ( distance ? distance : 1e-3 f ) ) ) / getDistance ( distance ) ) * stifness ;
2023-06-23 21:43:12 +02:00
localForce * = ( distancesBetweenZones [ zone . second - > getId ( ) ] [ otherZone - > getId ( ) ] ) ;
forceVector - = localForce * scaleForceBetweenZones ( zone . second , otherZone ) ;
2023-06-17 19:09:38 +02:00
}
}
2016-07-12 21:23:45 +02:00
overlaps [ zone . second ] = overlap ;
forceVector . z = 0 ; //operator - doesn't preserve z coordinate :/
forces [ zone . second ] = forceVector ;
}
}
2023-04-20 12:24:57 +02:00
void CZonePlacer : : moveOneZone ( TZoneMap & zones , TForceVector & totalForces , TDistanceVector & distances , TDistanceVector & overlaps )
2016-07-13 07:54:52 +02:00
{
2023-06-23 21:43:12 +02:00
//The more zones, the greater total distance expected
//Also, higher stiffness make expected movement lower
const int maxDistanceMovementRatio = zones . size ( ) * zones . size ( ) * ( stiffnessConstant / stifness ) ;
2023-04-20 12:24:57 +02:00
typedef std : : pair < float , std : : shared_ptr < Zone > > Misplacement ;
std : : vector < Misplacement > misplacedZones ;
2016-07-13 07:54:52 +02:00
float totalDistance = 0 ;
float totalOverlap = 0 ;
2023-04-20 12:24:57 +02:00
for ( const auto & zone : distances ) //find most misplaced zone
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
if ( vstd : : contains ( lastSwappedZones , zone . first - > getId ( ) ) )
{
continue ;
}
2016-07-13 07:54:52 +02:00
totalDistance + = zone . second ;
float overlap = overlaps [ zone . first ] ;
totalOverlap + = overlap ;
2023-04-16 21:12:21 +02:00
//if distance to actual movement is long, the zone is misplaced
float ratio = ( zone . second + overlap ) / static_cast < float > ( totalForces [ zone . first ] . mag ( ) ) ;
2023-04-20 12:24:57 +02:00
if ( ratio > maxDistanceMovementRatio )
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
misplacedZones . emplace_back ( std : : make_pair ( ratio , zone . first ) ) ;
2016-07-13 07:54:52 +02:00
}
}
2023-04-20 12:24:57 +02:00
if ( misplacedZones . empty ( ) )
return ;
boost : : sort ( misplacedZones , [ ] ( const Misplacement & lhs , Misplacement & rhs )
{
2023-06-23 21:43:12 +02:00
return lhs . first > rhs . first ; //Largest dispalcement first
2023-04-20 12:24:57 +02:00
} ) ;
logGlobal - > trace ( " Worst misplacement/movement ratio: %3.2f " , misplacedZones . front ( ) . first ) ;
if ( misplacedZones . size ( ) > = 2 )
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
//Swap 2 misplaced zones
2016-07-13 07:54:52 +02:00
2023-04-20 12:24:57 +02:00
auto firstZone = misplacedZones . front ( ) . second ;
std : : shared_ptr < Zone > secondZone ;
2023-06-23 21:43:12 +02:00
std : : set < TRmgTemplateZoneId > connectedZones ;
for ( const auto & connection : firstZone - > getConnections ( ) )
{
//FIXME: Should we also exclude fictive connections?
2023-07-07 16:32:17 +02:00
if ( connection . getConnectionType ( ) ! = rmg : : EConnectionType : : REPULSIVE )
2023-06-23 21:43:12 +02:00
{
connectedZones . insert ( connection . getOtherZoneId ( firstZone - > getId ( ) ) ) ;
}
}
2023-04-20 12:24:57 +02:00
auto level = firstZone - > getCenter ( ) . z ;
for ( size_t i = 1 ; i < misplacedZones . size ( ) ; i + + )
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
//Only swap zones on the same level
//Don't swap zones that should be connected (Jebus)
2023-06-23 21:43:12 +02:00
2023-04-20 12:24:57 +02:00
if ( misplacedZones [ i ] . second - > getCenter ( ) . z = = level & &
2023-06-23 21:43:12 +02:00
! vstd : : contains ( connectedZones , misplacedZones [ i ] . second - > getId ( ) ) )
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
secondZone = misplacedZones [ i ] . second ;
break ;
2016-07-13 07:54:52 +02:00
}
2023-04-20 12:24:57 +02:00
}
if ( secondZone )
{
logGlobal - > trace ( " Swapping two misplaced zones %d and %d " , firstZone - > getId ( ) , secondZone - > getId ( ) ) ;
2017-05-27 21:37:05 +02:00
2023-04-20 12:24:57 +02:00
auto firstCenter = firstZone - > getCenter ( ) ;
auto secondCenter = secondZone - > getCenter ( ) ;
firstZone - > setCenter ( secondCenter ) ;
secondZone - > setCenter ( firstCenter ) ;
lastSwappedZones . insert ( firstZone - > getId ( ) ) ;
lastSwappedZones . insert ( secondZone - > getId ( ) ) ;
return ;
2016-07-13 07:54:52 +02:00
}
2023-04-20 12:24:57 +02:00
}
lastSwappedZones . clear ( ) ; //If we didn't swap zones in this iteration, we can do it in the next
//find most distant zone that should be attracted and move inside it
std : : shared_ptr < Zone > targetZone ;
auto misplacedZone = misplacedZones . front ( ) . second ;
float3 ourCenter = misplacedZone - > getCenter ( ) ;
2023-04-20 12:44:32 +02:00
if ( ( totalDistance / ( bestTotalDistance + 1 ) ) > ( totalOverlap / ( bestTotalOverlap + 1 ) ) )
2023-04-20 12:24:57 +02:00
{
//Move one zone towards most distant zone to reduce distance
float maxDistance = 0 ;
2023-06-23 21:43:12 +02:00
for ( auto con : misplacedZone - > getConnections ( ) )
2016-07-13 07:54:52 +02:00
{
2023-07-07 16:32:17 +02:00
if ( con . getConnectionType ( ) = = rmg : : EConnectionType : : REPULSIVE )
2023-06-23 21:43:12 +02:00
{
continue ;
}
auto otherZone = zones [ con . getOtherZoneId ( misplacedZone - > getId ( ) ) ] ;
2023-04-20 12:24:57 +02:00
float distance = static_cast < float > ( otherZone - > getCenter ( ) . dist2dSQ ( ourCenter ) ) ;
if ( distance > maxDistance )
2016-07-13 07:54:52 +02:00
{
2023-04-20 12:24:57 +02:00
maxDistance = distance ;
targetZone = otherZone ;
}
}
2023-04-20 12:44:32 +02:00
if ( targetZone )
2023-04-20 12:24:57 +02:00
{
float3 vec = targetZone - > getCenter ( ) - ourCenter ;
float newDistanceBetweenZones = ( std : : max ( misplacedZone - > getSize ( ) , targetZone - > getSize ( ) ) ) / mapSize ;
2023-04-20 16:26:06 +02:00
logGlobal - > trace ( " Trying to move zone %d %s towards %d %s. Direction is %s " , misplacedZone - > getId ( ) , ourCenter . toString ( ) , targetZone - > getId ( ) , targetZone - > getCenter ( ) . toString ( ) , vec . toString ( ) ) ;
2016-07-13 07:54:52 +02:00
2023-04-20 12:24:57 +02:00
misplacedZone - > setCenter ( targetZone - > getCenter ( ) - vec . unitVector ( ) * newDistanceBetweenZones ) ; //zones should now overlap by half size
}
}
else
{
//Move misplaced zone away from overlapping zone
2016-07-13 07:54:52 +02:00
2023-04-20 12:24:57 +02:00
float maxOverlap = 0 ;
for ( const auto & otherZone : zones )
{
float3 otherZoneCenter = otherZone . second - > getCenter ( ) ;
if ( otherZone . second = = misplacedZone | | otherZoneCenter . z ! = ourCenter . z )
continue ;
2016-11-25 20:32:54 +02:00
2023-04-20 12:24:57 +02:00
auto distance = static_cast < float > ( otherZoneCenter . dist2dSQ ( ourCenter ) ) ;
if ( distance > maxOverlap )
{
maxOverlap = distance ;
targetZone = otherZone . second ;
2016-11-25 20:32:54 +02:00
}
2016-07-13 07:54:52 +02:00
}
2023-04-20 12:24:57 +02:00
if ( targetZone )
{
float3 vec = ourCenter - targetZone - > getCenter ( ) ;
float newDistanceBetweenZones = ( misplacedZone - > getSize ( ) + targetZone - > getSize ( ) ) / mapSize ;
2023-04-20 16:26:06 +02:00
logGlobal - > trace ( " Trying to move zone %d %s away from %d %s. Direction is %s " , misplacedZone - > getId ( ) , ourCenter . toString ( ) , targetZone - > getId ( ) , targetZone - > getCenter ( ) . toString ( ) , vec . toString ( ) ) ;
2023-04-20 12:24:57 +02:00
misplacedZone - > setCenter ( targetZone - > getCenter ( ) + vec . unitVector ( ) * newDistanceBetweenZones ) ; //zones should now be just separated
}
2016-07-13 07:54:52 +02:00
}
2023-04-20 12:24:57 +02:00
//Don't swap that zone in next iteration
lastSwappedZones . insert ( misplacedZone - > getId ( ) ) ;
2016-07-13 07:54:52 +02:00
}
2014-05-25 12:02:15 +03:00
float CZonePlacer : : metric ( const int3 & A , const int3 & B ) const
2014-05-24 15:06:08 +03:00
{
float dx = abs ( A . x - B . x ) * scaleX ;
float dy = abs ( A . y - B . y ) * scaleY ;
2023-04-18 22:01:51 +02:00
/*
1. Normal euclidean distance
2. Sinus for extra curves
3. Nonlinear mess for fuzzy edges
*/
return dx * dx + dy * dy +
5 * std : : sin ( dx * dy / 10 ) +
2023-04-18 22:34:26 +02:00
25 * std : : sin ( std : : sqrt ( A . x * B . x ) * ( A . y - B . y ) / 100 * ( scaleX * scaleY ) ) ;
2014-05-24 15:06:08 +03:00
}
2022-08-09 07:54:32 +02:00
void CZonePlacer : : assignZones ( CRandomGenerator * rand )
2014-05-24 15:06:08 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > info ( " Starting zone colouring " ) ;
2014-05-24 19:39:58 +03:00
2022-08-09 07:54:32 +02:00
auto width = map . getMapGenOptions ( ) . getWidth ( ) ;
auto height = map . getMapGenOptions ( ) . getHeight ( ) ;
2014-05-24 15:06:08 +03:00
//scale to Medium map to ensure smooth results
scaleX = 72.f / width ;
scaleY = 72.f / height ;
2022-08-09 07:54:32 +02:00
auto zones = map . getZones ( ) ;
vstd : : erase_if ( zones , [ ] ( const std : : pair < TRmgTemplateZoneId , std : : shared_ptr < Zone > > & pr )
{
return pr . second - > getType ( ) = = ETemplateZoneType : : WATER ;
} ) ;
2014-05-24 15:06:08 +03:00
2023-02-11 18:05:02 +02:00
using Dpair = std : : pair < std : : shared_ptr < Zone > , float > ;
2014-05-24 15:06:08 +03:00
std : : vector < Dpair > distances ;
distances . reserve ( zones . size ( ) ) ;
2015-01-16 20:28:27 +02:00
//now place zones correctly and assign tiles to each zone
2014-05-24 15:06:08 +03:00
auto compareByDistance = [ ] ( const Dpair & lhs , const Dpair & rhs ) - > bool
{
2016-07-13 10:35:31 +02:00
//bigger zones have smaller distance
return lhs . second / lhs . first - > getSize ( ) < rhs . second / rhs . first - > getSize ( ) ;
2014-05-24 15:06:08 +03:00
} ;
2023-02-11 18:05:02 +02:00
auto moveZoneToCenterOfMass = [ ] ( const std : : shared_ptr < Zone > & zone ) - > void
2015-01-16 20:28:27 +02:00
{
int3 total ( 0 , 0 , 0 ) ;
2022-08-09 07:54:32 +02:00
auto tiles = zone - > area ( ) . getTiles ( ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & tile : tiles )
2015-01-16 20:28:27 +02:00
{
total + = tile ;
}
2020-10-01 10:38:06 +02:00
int size = static_cast < int > ( tiles . size ( ) ) ;
2015-01-16 20:28:27 +02:00
assert ( size ) ;
zone - > setPos ( int3 ( total . x / size , total . y / size , total . z / size ) ) ;
} ;
2023-05-19 20:30:15 +02:00
int levels = map . levels ( ) ;
2015-01-16 20:28:27 +02:00
/*
1. Create Voronoi diagram
2. find current center of mass for each zone . Move zone to that center to balance zones sizes
*/
2022-09-18 16:39:10 +02:00
int3 pos ;
for ( pos . z = 0 ; pos . z < levels ; pos . z + + )
2015-01-16 20:28:27 +02:00
{
2022-09-18 16:39:10 +02:00
for ( pos . x = 0 ; pos . x < width ; pos . x + + )
2015-01-16 20:28:27 +02:00
{
2022-09-18 16:39:10 +02:00
for ( pos . y = 0 ; pos . y < height ; pos . y + + )
2015-01-16 20:28:27 +02:00
{
distances . clear ( ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2015-01-16 20:28:27 +02:00
{
2022-09-18 16:39:10 +02:00
if ( zone . second - > getPos ( ) . z = = pos . z )
2023-02-11 18:05:02 +02:00
distances . emplace_back ( zone . second , static_cast < float > ( pos . dist2dSQ ( zone . second - > getPos ( ) ) ) ) ;
2015-01-16 20:28:27 +02:00
else
2023-02-11 18:05:02 +02:00
distances . emplace_back ( zone . second , std : : numeric_limits < float > : : max ( ) ) ;
2015-01-16 20:28:27 +02:00
}
2022-08-09 07:54:32 +02:00
boost : : min_element ( distances , compareByDistance ) - > first - > area ( ) . add ( pos ) ; //closest tile belongs to zone
2015-01-16 20:28:27 +02:00
}
}
}
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2022-12-06 13:07:16 +02:00
{
if ( zone . second - > area ( ) . empty ( ) )
throw rmgException ( " Empty zone is generated, probably RMG template is inappropriate for map size " ) ;
2015-01-16 20:28:27 +02:00
moveZoneToCenterOfMass ( zone . second ) ;
2022-12-06 13:07:16 +02:00
}
2015-01-16 20:28:27 +02:00
//assign actual tiles to each zone using nonlinear norm for fine edges
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2015-01-16 20:28:27 +02:00
zone . second - > clearTiles ( ) ; //now populate them again
2022-09-18 16:39:10 +02:00
for ( pos . z = 0 ; pos . z < levels ; pos . z + + )
2014-05-24 15:06:08 +03:00
{
2022-09-18 16:39:10 +02:00
for ( pos . x = 0 ; pos . x < width ; pos . x + + )
2014-05-24 15:06:08 +03:00
{
2022-09-18 16:39:10 +02:00
for ( pos . y = 0 ; pos . y < height ; pos . y + + )
2014-05-24 15:06:08 +03:00
{
distances . clear ( ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2014-05-24 15:06:08 +03:00
{
2022-09-18 16:39:10 +02:00
if ( zone . second - > getPos ( ) . z = = pos . z )
2023-02-11 18:05:02 +02:00
distances . emplace_back ( zone . second , metric ( pos , zone . second - > getPos ( ) ) ) ;
2014-07-03 13:28:51 +03:00
else
2023-02-11 18:05:02 +02:00
distances . emplace_back ( zone . second , std : : numeric_limits < float > : : max ( ) ) ;
2014-05-24 15:06:08 +03:00
}
2016-08-09 10:12:13 +02:00
auto zone = boost : : min_element ( distances , compareByDistance ) - > first ; //closest tile belongs to zone
2022-08-09 07:54:32 +02:00
zone - > area ( ) . add ( pos ) ;
map . setZoneID ( pos , zone - > getId ( ) ) ;
2014-05-24 15:06:08 +03:00
}
}
}
2015-01-16 20:28:27 +02:00
//set position (town position) to center of mass of irregular zone
2023-02-11 18:05:02 +02:00
for ( const auto & zone : zones )
2014-06-01 22:01:18 +03:00
{
2015-01-16 20:28:27 +02:00
moveZoneToCenterOfMass ( zone . second ) ;
2014-07-03 13:28:51 +03:00
//TODO: similiar for islands
2014-10-31 19:47:10 +02:00
# define CREATE_FULL_UNDERGROUND true //consider linking this with water amount
2022-05-28 15:03:50 +02:00
if ( zone . second - > isUnderground ( ) )
2014-07-03 18:24:28 +03:00
{
2014-10-31 19:47:10 +02:00
if ( ! CREATE_FULL_UNDERGROUND )
2022-05-31 11:25:39 +02:00
{
2022-08-09 07:54:32 +02:00
auto discardTiles = collectDistantTiles ( * zone . second , zone . second - > getSize ( ) + 1.f ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & t : discardTiles )
2022-08-09 07:54:32 +02:00
zone . second - > area ( ) . erase ( t ) ;
2022-05-31 11:25:39 +02:00
}
2014-07-03 18:24:28 +03:00
//make sure that terrain inside zone is not a rock
2023-05-20 07:55:28 +02:00
auto v = zone . second - > getArea ( ) . getTilesVector ( ) ;
map . getMapProxy ( ) - > drawTerrain ( * rand , v , ETerrainId : : SUBTERRANEAN ) ;
2014-07-03 18:24:28 +03:00
}
2014-06-01 22:01:18 +03:00
}
2017-08-10 18:39:27 +02:00
logGlobal - > info ( " Finished zone colouring " ) ;
2014-05-24 15:06:08 +03:00
}
2022-07-26 15:07:42 +02:00
2023-04-23 10:08:16 +02:00
const TDistanceMap & CZonePlacer : : getDistanceMap ( )
{
return distancesBetweenZones ;
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END