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"
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 )
2016-11-27 21:37:41 +02:00
: width ( 0 ) , height ( 0 ) , scaleX ( 0 ) , scaleY ( 0 ) , mapSize ( 0 ) , gravityConstant ( 0 ) , stiffnessConstant ( 0 ) ,
2022-08-09 07:54:32 +02:00
map ( map )
2013-08-17 15:46:48 +03:00
{
}
CZonePlacer : : ~ CZonePlacer ( )
{
}
2014-05-25 12:02:15 +03:00
int3 CZonePlacer : : cords ( const float3 f ) const
2014-05-24 13:42:06 +03:00
{
2022-08-09 07:54:32 +02:00
return int3 ( ( si32 ) std : : max ( 0.f , ( f . x * map . map ( ) . width ) - 1 ) , ( si32 ) std : : max ( 0.f , ( f . y * map . 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
}
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
2014-05-24 23:10:46 +03:00
/*
2016-07-13 07:54:52 +02:00
gravity - based algorithm
2014-05-24 23:10:46 +03:00
2016-07-13 07:54:52 +02:00
let ' s assume we try to fit N circular zones with radius = size on a map
2014-05-24 23:10:46 +03:00
*/
2014-07-06 11:43:30 +03:00
2020-10-01 10:38:06 +02:00
gravityConstant = 4e-3 f ;
stiffnessConstant = 4e-3 f ;
2014-12-23 14:49:07 +02: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
2020-10-01 10:38:06 +02:00
//gravity-based algorithm. connected zones attract, intersecting zones and map boundaries push back
2014-05-24 23:10:46 +03:00
2014-12-22 22:33:37 +02:00
//remember best solution
float bestTotalDistance = 1e10 ;
2014-12-23 00:35:19 +02:00
float bestTotalOverlap = 1e10 ;
2014-12-22 22:33:37 +02: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
const int MAX_ITERATIONS = 100 ;
for ( int i = 0 ; i < MAX_ITERATIONS ; + + i ) //until zones reach their desired size and fill the map tightly
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 ) ;
2016-07-10 18:16:32 +02:00
for ( auto zone : forces )
{
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 ) ;
2014-05-24 23:10:46 +03:00
for ( auto zone : forces )
{
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
2016-07-12 21:23:45 +02:00
//3. now perform drastic movement of zone that is completely not linked
2014-10-31 13:58:55 +02:00
2016-07-13 07:54:52 +02:00
moveOneZone ( zones , totalForces , distances , overlaps ) ;
2016-07-12 21:23:45 +02:00
//4. NOW after everything was moved, re-evaluate zone positions
attractConnectedZones ( zones , forces , distances ) ;
separateOverlappingZones ( zones , forces , overlaps ) ;
2016-07-13 07:54:52 +02:00
float totalDistance = 0 ;
float totalOverlap = 0 ;
2016-07-12 21:23:45 +02:00
for ( auto zone : distances ) //find most misplaced zone
{
totalDistance + = zone . second ;
float overlap = overlaps [ zone . first ] ;
totalOverlap + = overlap ;
}
//check fitness function
bool improvement = false ;
if ( bestTotalDistance > 0 & & bestTotalOverlap > 0 )
{
if ( totalDistance * totalOverlap < bestTotalDistance * bestTotalOverlap ) //multiplication is better for auto-scaling, but stops working if one factor is 0
improvement = true ;
}
else
2017-07-19 02:42:26 +02:00
{
2016-07-12 21:23:45 +02:00
if ( totalDistance + totalOverlap < bestTotalDistance + bestTotalOverlap )
improvement = true ;
2017-07-19 02:42:26 +02:00
}
2016-07-12 21:23:45 +02:00
2017-08-10 20:59:55 +02:00
logGlobal - > trace ( " Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s " , totalDistance , totalOverlap , improvement ) ;
2016-07-12 21:23:45 +02:00
//save best solution
if ( improvement )
{
bestTotalDistance = totalDistance ;
bestTotalOverlap = totalOverlap ;
for ( auto zone : zones )
bestSolution [ zone . second ] = zone . second - > getCenter ( ) ;
}
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 ) ;
2014-05-24 13:42:06 +03:00
for ( auto zone : zones ) //finalize zone positions
{
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
const float radius = 0.4f ;
const float pi2 = 6.28f ;
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
for ( 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
{
2016-08-06 12:16:13 +02:00
if ( boost : : 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 ( ) ;
2016-08-06 12:16:13 +02:00
si32 faction = CMapGenOptions : : CPlayerSettings : : RANDOM_TOWN ;
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
if ( faction = = CMapGenOptions : : CPlayerSettings : : RANDOM_TOWN ) //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
}
}
for ( auto zone : zonesToPlace )
{
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 ;
}
for ( auto zone : zonesVector )
{
int level = levels [ zone . first ] ;
totalSize [ level ] + = ( zone . second - > getSize ( ) * zone . second - > getSize ( ) ) ;
2020-10-01 10:38:06 +02:00
float randomAngle = static_cast < float > ( rand - > nextDouble ( 0 , pi2 ) ) ;
2016-07-13 07:54:52 +02:00
zone . second - > setCenter ( float3 ( 0.5f + std : : sin ( randomAngle ) * radius , 0.5f + std : : cos ( randomAngle ) * radius , level ) ) ; //place zones around circle
}
/*
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 + + )
prescaler [ i ] = sqrt ( ( width * height ) / ( totalSize [ i ] * 3.14f ) ) ;
2020-10-01 10:38:06 +02:00
mapSize = static_cast < float > ( sqrt ( width * height ) ) ;
2016-07-13 07:54:52 +02:00
for ( auto zone : zones )
{
2020-10-01 10:38:06 +02:00
zone . second - > setSize ( ( int ) ( zone . second - > getSize ( ) * prescaler [ zone . second - > getCenter ( ) . z ] ) ) ;
2016-07-13 07:54:52 +02:00
}
}
2016-07-12 21:23:45 +02:00
void CZonePlacer : : attractConnectedZones ( TZoneMap & zones , TForceVector & forces , TDistanceVector & distances )
{
for ( auto zone : zones )
{
float3 forceVector ( 0 , 0 , 0 ) ;
float3 pos = zone . second - > getCenter ( ) ;
float totalDistance = 0 ;
for ( auto con : zone . second - > getConnections ( ) )
{
auto otherZone = zones [ con ] ;
float3 otherZoneCenter = otherZone - > getCenter ( ) ;
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( pos . dist2d ( otherZoneCenter ) ) ;
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 )
{
//WARNING: compiler used to 'optimize' that line so it never actually worked
float overlapMultiplier = ( pos . z = = otherZoneCenter . z ) ? ( minDistance / distance ) : 1.0f ;
2017-10-29 17:23:30 +02:00
forceVector + = ( ( otherZoneCenter - pos ) * overlapMultiplier / getDistance ( distance ) ) * gravityConstant ; //positive value
2016-07-12 21:23:45 +02:00
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 )
{
for ( auto zone : zones )
{
float3 forceVector ( 0 , 0 , 0 ) ;
float3 pos = zone . second - > getCenter ( ) ;
float overlap = 0 ;
2020-10-01 10:38:06 +02:00
//separate overlapping zones
2016-07-12 21:23:45 +02:00
for ( auto otherZone : zones )
{
float3 otherZoneCenter = otherZone . second - > getCenter ( ) ;
//zones on different levels don't push away
if ( zone = = otherZone | | pos . z ! = otherZoneCenter . z )
continue ;
2020-10-01 10:38:06 +02:00
float 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 )
{
2020-10-01 10:38:06 +02:00
forceVector - = ( ( ( otherZoneCenter - pos ) * ( minDistance / ( distance ? distance : 1e-3 f ) ) ) / getDistance ( distance ) ) * stiffnessConstant ; //negative value
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 ) ;
2020-10-01 10:38:06 +02:00
float 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
forceVector - = ( boundary - pos ) * ( size - distance ) / this - > getDistance ( distance ) * this - > stiffnessConstant ; //negative value
} ;
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 ) ;
}
overlaps [ zone . second ] = overlap ;
forceVector . z = 0 ; //operator - doesn't preserve z coordinate :/
forces [ zone . second ] = forceVector ;
}
}
2016-07-13 07:54:52 +02:00
void CZonePlacer : : moveOneZone ( TZoneMap & zones , TForceVector & totalForces , TDistanceVector & distances , TDistanceVector & overlaps )
{
float maxRatio = 0 ;
2020-10-01 10:38:06 +02:00
const int maxDistanceMovementRatio = static_cast < int > ( zones . size ( ) * zones . size ( ) ) ; //experimental - the more zones, the greater total distance expected
2022-08-09 07:54:32 +02:00
std : : shared_ptr < Zone > misplacedZone ;
2016-07-13 07:54:52 +02:00
float totalDistance = 0 ;
float totalOverlap = 0 ;
for ( auto zone : distances ) //find most misplaced zone
{
totalDistance + = zone . second ;
float overlap = overlaps [ zone . first ] ;
totalOverlap + = overlap ;
2020-10-01 10:38:06 +02:00
float ratio = ( zone . second + overlap ) / ( float ) totalForces [ zone . first ] . mag ( ) ; //if distance to actual movement is long, the zone is misplaced
2016-07-13 07:54:52 +02:00
if ( ratio > maxRatio )
{
maxRatio = ratio ;
misplacedZone = zone . first ;
}
}
2017-08-10 18:39:27 +02:00
logGlobal - > trace ( " Worst misplacement/movement ratio: %3.2f " , maxRatio ) ;
2016-07-13 07:54:52 +02:00
2016-11-25 20:32:54 +02:00
if ( maxRatio > maxDistanceMovementRatio & & misplacedZone )
2016-07-13 07:54:52 +02:00
{
2022-08-09 07:54:32 +02:00
std : : shared_ptr < Zone > targetZone ;
2016-07-13 07:54:52 +02:00
float3 ourCenter = misplacedZone - > getCenter ( ) ;
if ( totalDistance > totalOverlap )
{
//find most distant zone that should be attracted and move inside it
float maxDistance = 0 ;
for ( auto con : misplacedZone - > getConnections ( ) )
{
auto otherZone = zones [ con ] ;
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( otherZone - > getCenter ( ) . dist2dSQ ( ourCenter ) ) ;
2016-07-13 07:54:52 +02:00
if ( distance > maxDistance )
{
maxDistance = distance ;
targetZone = otherZone ;
}
}
2017-05-27 21:37:05 +02:00
if ( targetZone ) //TODO: consider refactoring duplicated code
{
float3 vec = targetZone - > getCenter ( ) - ourCenter ;
float newDistanceBetweenZones = ( std : : max ( misplacedZone - > getSize ( ) , targetZone - > getSize ( ) ) ) / mapSize ;
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Trying to move zone %d %s towards %d %s. Old distance %f " , misplacedZone - > getId ( ) , ourCenter . toString ( ) , targetZone - > getId ( ) , targetZone - > getCenter ( ) . toString ( ) , maxDistance ) ;
logGlobal - > trace ( " direction is %s " , vec . toString ( ) ) ;
2017-05-27 21:37:05 +02:00
misplacedZone - > setCenter ( targetZone - > getCenter ( ) - vec . unitVector ( ) * newDistanceBetweenZones ) ; //zones should now overlap by half size
2017-08-10 18:39:27 +02:00
logGlobal - > trace ( " New distance %f " , targetZone - > getCenter ( ) . dist2d ( misplacedZone - > getCenter ( ) ) ) ;
2017-05-27 21:37:05 +02:00
}
2016-07-13 07:54:52 +02:00
}
else
{
float maxOverlap = 0 ;
for ( auto otherZone : zones )
{
float3 otherZoneCenter = otherZone . second - > getCenter ( ) ;
if ( otherZone . second = = misplacedZone | | otherZoneCenter . z ! = ourCenter . z )
continue ;
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( otherZoneCenter . dist2dSQ ( ourCenter ) ) ;
2016-07-13 07:54:52 +02:00
if ( distance > maxOverlap )
{
maxOverlap = distance ;
targetZone = otherZone . second ;
}
}
2016-11-25 20:32:54 +02:00
if ( targetZone )
{
float3 vec = ourCenter - targetZone - > getCenter ( ) ;
float newDistanceBetweenZones = ( misplacedZone - > getSize ( ) + targetZone - > getSize ( ) ) / mapSize ;
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Trying to move zone %d %s away from %d %s. Old distance %f " , misplacedZone - > getId ( ) , ourCenter . toString ( ) , targetZone - > getId ( ) , targetZone - > getCenter ( ) . toString ( ) , maxOverlap ) ;
logGlobal - > trace ( " direction is %s " , vec . toString ( ) ) ;
2016-11-25 20:32:54 +02:00
misplacedZone - > setCenter ( targetZone - > getCenter ( ) + vec . unitVector ( ) * newDistanceBetweenZones ) ; //zones should now be just separated
2017-08-10 18:39:27 +02:00
logGlobal - > trace ( " New distance %f " , targetZone - > getCenter ( ) . dist2d ( misplacedZone - > getCenter ( ) ) ) ;
2016-11-25 20:32:54 +02:00
}
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
{
/*
Matlab code
dx = abs ( A ( 1 ) - B ( 1 ) ) ; % distance must be symmetric
dy = abs ( A ( 2 ) - B ( 2 ) ) ;
2014-05-25 14:30:47 +03:00
d = 0.01 * dx ^ 3 - 0.1618 * dx ^ 2 + 1 * dx + . . .
0.01618 * dy ^ 3 + 0.1 * dy ^ 2 + 0.168 * dy ;
2014-05-24 15:06:08 +03:00
*/
float dx = abs ( A . x - B . x ) * scaleX ;
float dy = abs ( A . y - B . y ) * scaleY ;
//Horner scheme
2020-10-01 10:38:06 +02:00
return dx * ( 1.0f + dx * ( 0.1f + dx * 0.01f ) ) + dy * ( 1.618f + dy * ( - 0.1618f + dy * 0.01618f ) ) ;
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
2022-08-09 07:54:32 +02:00
typedef std : : pair < std : : shared_ptr < Zone > , float > Dpair ;
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
} ;
2022-08-09 07:54:32 +02:00
auto moveZoneToCenterOfMass = [ ] ( 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 ( ) ;
2015-01-16 20:28:27 +02:00
for ( auto tile : tiles )
{
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 ) ) ;
} ;
2022-09-18 16:39:10 +02:00
int levels = map . 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 ( ) ;
2022-09-18 16:39:10 +02:00
for ( 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 )
2020-10-01 10:38:06 +02:00
distances . push_back ( std : : make_pair ( zone . second , ( float ) pos . dist2dSQ ( zone . second - > getPos ( ) ) ) ) ;
2015-01-16 20:28:27 +02:00
else
distances . push_back ( std : : make_pair ( zone . second , std : : numeric_limits < float > : : max ( ) ) ) ;
}
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
}
}
}
for ( 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
for ( auto zone : zones )
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 ( ) ;
for ( auto zone : zones )
{
2022-09-18 16:39:10 +02:00
if ( zone . second - > getPos ( ) . z = = pos . z )
2014-07-03 13:28:51 +03:00
distances . push_back ( std : : make_pair ( zone . second , metric ( pos , zone . second - > getPos ( ) ) ) ) ;
else
distances . push_back ( std : : make_pair ( 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
2014-06-01 22:01:18 +03:00
for ( auto zone : zones )
{
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 ) ;
for ( auto & t : discardTiles )
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
//FIXME: reorder actions?
2022-12-20 18:35:40 +02:00
paintZoneTerrain ( * zone . second , * rand , map , 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
VCMI_LIB_NAMESPACE_END