2013-08-17 12:46:48 +00: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 12:42:06 +02:00
# include "../CRandomGenerator.h"
2013-08-17 12:46:48 +00:00
# include "CZonePlacer.h"
2014-05-24 12:42:06 +02:00
# include "CRmgTemplateZone.h"
2013-08-17 12:46:48 +00:00
# include "CZoneGraphGenerator.h"
2014-05-24 12:42:06 +02:00
class CRandomGenerator ;
CPlacedZone : : CPlacedZone ( const CRmgTemplateZone * zone ) : zone ( zone )
2013-08-17 12:46:48 +00:00
{
}
2014-05-24 12:42:06 +02:00
CZonePlacer : : CZonePlacer ( CMapGenerator * Gen ) : gen ( Gen )
2013-08-17 12:46:48 +00:00
{
}
CZonePlacer : : ~ CZonePlacer ( )
{
}
2014-05-25 13:02:15 +04:00
int3 CZonePlacer : : cords ( const float3 f ) const
2014-05-24 12:42:06 +02:00
{
2014-05-24 22:10:46 +02:00
return int3 ( std : : max ( 0.f , ( f . x * gen - > map - > width ) - 1 ) , std : : max ( 0.f , ( f . y * gen - > map - > height - 1 ) ) , f . z ) ;
2014-05-24 12:42:06 +02:00
}
void CZonePlacer : : placeZones ( shared_ptr < CMapGenOptions > mapGenOptions , CRandomGenerator * rand )
2013-08-17 12:46:48 +00:00
{
2014-05-24 12:42:06 +02:00
//some relaxation-simmulated annealing algorithm
2014-05-24 18:39:58 +02:00
const int iterations = 100 ;
2014-05-24 22:10:46 +02:00
float temperature = 1e-2 ; ;
2014-05-24 18:39:58 +02:00
const float temperatureModifier = 0.99 ;
2014-05-24 12:42:06 +02:00
logGlobal - > infoStream ( ) < < " Starting zone placement " ;
int width = mapGenOptions - > getWidth ( ) ;
int height = mapGenOptions - > getHeight ( ) ;
auto zones = gen - > getZones ( ) ;
//TODO: consider underground zones
2014-05-24 22:10:46 +02:00
/*
let ' s assume we try to fit N circular zones with radius = size on a map
formula : sum ( ( prescaler * n ) ^ 2 ) * pi = WH
prescaler = sqrt ( ( WH ) / ( sum ( n ^ 2 ) * pi ) )
*/
2014-05-24 12:42:06 +02:00
float totalSize = 0 ;
for ( auto zone : zones )
{
2014-05-24 22:10:46 +02:00
totalSize + = ( zone . second - > getSize ( ) * zone . second - > getSize ( ) ) ;
2014-05-24 18:39:58 +02:00
zone . second - > setCenter ( float3 ( rand - > nextDouble ( 0.2 , 0.8 ) , rand - > nextDouble ( 0.2 , 0.8 ) , 0 ) ) ; //start away from borders
2014-05-24 12:42:06 +02:00
}
//prescale zones
2014-05-24 22:10:46 +02:00
float prescaler = sqrt ( ( width * height ) / ( totalSize * 3.14f ) ) ;
2014-05-24 12:42:06 +02:00
float mapSize = sqrt ( width * height ) ;
for ( auto zone : zones )
{
zone . second - > setSize ( zone . second - > getSize ( ) * prescaler ) ;
}
2014-05-24 22:10:46 +02:00
//gravity-based algorithm. connected zones attract, intersceting zones and map boundaries push back
auto getDistance = [ ] ( float distance ) - > float
{
return ( distance ? distance * distance : 1e-6 ) ;
} ;
std : : map < CRmgTemplateZone * , float3 > forces ;
2014-05-24 12:42:06 +02:00
for ( int i = 0 ; i < iterations ; + + i )
{
for ( auto zone : zones )
{
2014-05-24 22:10:46 +02:00
float3 forceVector ( 0 , 0 , 0 ) ;
float3 pos = zone . second - > getCenter ( ) ;
2014-05-24 12:42:06 +02:00
//attract connected zones
for ( auto con : zone . second - > getConnections ( ) )
{
auto otherZone = zones [ con ] ;
2014-05-24 22:10:46 +02:00
float distance = pos . dist2d ( otherZone - > getCenter ( ) ) ;
2014-05-24 12:42:06 +02:00
float minDistance = ( zone . second - > getSize ( ) + otherZone - > getSize ( ) ) / mapSize ; //scale down to (0,1) coordinates
if ( distance > minDistance )
{
2014-05-24 22:10:46 +02:00
forceVector + = ( otherZone - > getCenter ( ) - pos ) / getDistance ( distance ) ; //positive value
2014-05-24 12:42:06 +02:00
}
}
//separate overlaping zones
for ( auto otherZone : zones )
{
if ( zone = = otherZone )
continue ;
2013-08-17 12:46:48 +00:00
2014-05-24 22:10:46 +02:00
float distance = pos . dist2d ( otherZone . second - > getCenter ( ) ) ;
2014-05-24 12:42:06 +02:00
float minDistance = ( zone . second - > getSize ( ) + otherZone . second - > getSize ( ) ) / mapSize ;
if ( distance < minDistance )
{
2014-05-24 22:10:46 +02:00
forceVector - = ( otherZone . second - > getCenter ( ) - pos ) / getDistance ( distance ) ; //negative value
2014-05-24 12:42:06 +02:00
}
}
2014-05-24 22:10:46 +02:00
2014-05-24 18:39:58 +02:00
//move zones away from boundaries
float3 boundary ( 0 , 0 , pos . z ) ;
float size = zone . second - > getSize ( ) / mapSize ;
if ( pos . x < size )
{
boundary = float3 ( 0 , pos . y , pos . z ) ;
2014-05-24 22:10:46 +02:00
float distance = pos . dist2d ( boundary ) ;
forceVector - = ( boundary - pos ) / getDistance ( distance ) ; //negative value
2014-05-24 18:39:58 +02:00
}
2014-05-24 22:10:46 +02:00
if ( pos . x > 1 - size )
2014-05-24 18:39:58 +02:00
{
2014-05-24 22:10:46 +02:00
boundary = float3 ( 1 , pos . y , pos . z ) ;
float distance = pos . dist2d ( boundary ) ;
forceVector - = ( boundary - pos ) / getDistance ( distance ) ; //negative value
2014-05-24 18:39:58 +02:00
}
2014-05-24 22:10:46 +02:00
if ( pos . y < size )
2014-05-24 18:39:58 +02:00
{
boundary = float3 ( pos . x , 0 , pos . z ) ;
2014-05-24 22:10:46 +02:00
float distance = pos . dist2d ( boundary ) ;
forceVector - = ( boundary - pos ) / getDistance ( distance ) ; //negative value
2014-05-24 18:39:58 +02:00
}
2014-05-24 22:10:46 +02:00
if ( pos . y > 1 - size )
2014-05-24 18:39:58 +02:00
{
2014-05-24 22:10:46 +02:00
boundary = float3 ( pos . x , 1 , pos . z ) ;
float distance = pos . dist2d ( boundary ) ;
forceVector - = ( boundary - pos ) / getDistance ( distance ) ; //negative value
2014-05-24 18:39:58 +02:00
}
2014-05-24 22:10:46 +02:00
forces [ zone . second ] = forceVector ;
}
//update positions
for ( auto zone : forces )
{
zone . first - > setCenter ( zone . first - > getCenter ( ) + zone . second * temperature ) ;
2014-05-24 18:39:58 +02:00
}
2014-05-24 22:10:46 +02:00
temperature * = temperatureModifier ; //decrease temperature (needed?)
2014-05-24 12:42:06 +02:00
}
for ( auto zone : zones ) //finalize zone positions
{
zone . second - > setPos ( cords ( zone . second - > getCenter ( ) ) ) ;
logGlobal - > infoStream ( ) < < boost : : format ( " Placed zone %d at relative position %s and coordinates %s " ) % zone . first % zone . second - > getCenter ( ) % zone . second - > getPos ( ) ;
}
2013-08-17 12:46:48 +00:00
}
2014-05-24 14:06:08 +02:00
2014-05-25 13:02:15 +04:00
float CZonePlacer : : metric ( const int3 & A , const int3 & B ) const
2014-05-24 14:06:08 +02:00
{
/*
Matlab code
dx = abs ( A ( 1 ) - B ( 1 ) ) ; % distance must be symmetric
dy = abs ( A ( 2 ) - B ( 2 ) ) ;
2014-05-25 13:30:47 +02: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 14:06:08 +02:00
*/
float dx = abs ( A . x - B . x ) * scaleX ;
float dy = abs ( A . y - B . y ) * scaleY ;
//Horner scheme
2014-05-28 21:11:10 +02:00
return dx * ( 1 + dx * ( 0.1 + dx * 0.01 ) ) + dy * ( 1.618 + dy * ( - 0.1618 + dy * 0.01618 ) ) ;
2014-05-24 14:06:08 +02:00
}
void CZonePlacer : : assignZones ( shared_ptr < CMapGenOptions > mapGenOptions )
{
2014-05-24 18:39:58 +02:00
logGlobal - > infoStream ( ) < < " Starting zone colouring " ;
2014-05-24 14:06:08 +02:00
auto width = mapGenOptions - > getWidth ( ) ;
auto height = mapGenOptions - > getHeight ( ) ;
//scale to Medium map to ensure smooth results
scaleX = 72.f / width ;
scaleY = 72.f / height ;
auto zones = gen - > getZones ( ) ;
typedef std : : pair < CRmgTemplateZone * , float > Dpair ;
std : : vector < Dpair > distances ;
distances . reserve ( zones . size ( ) ) ;
auto compareByDistance = [ ] ( const Dpair & lhs , const Dpair & rhs ) - > bool
{
return lhs . second < rhs . second ;
} ;
int levels = gen - > map - > twoLevel ? 2 : 1 ;
for ( int i = 0 ; i < width ; i + + )
{
for ( int j = 0 ; j < height ; j + + )
{
for ( int k = 0 ; k < levels ; k + + )
{
distances . clear ( ) ;
int3 pos ( i , j , k ) ;
for ( auto zone : zones )
{
distances . push_back ( std : : make_pair ( zone . second , metric ( pos , zone . second - > getPos ( ) ) ) ) ;
}
boost : : sort ( distances , compareByDistance ) ;
distances . front ( ) . first - > addTile ( pos ) ; //closest tile belongs to zone
}
}
}
2014-06-01 21:01:18 +02:00
//set position to center of mass
for ( auto zone : zones )
{
int3 total ( 0 , 0 , 0 ) ;
auto tiles = zone . second - > getTileInfo ( ) ;
for ( auto tile : tiles )
{
total + = tile ;
}
int size = tiles . size ( ) ;
zone . second - > setPos ( int3 ( total . x / size , total . y / size , total . z / size ) ) ;
}
2014-05-24 18:39:58 +02:00
logGlobal - > infoStream ( ) < < " Finished zone colouring " ;
2014-05-24 14:06:08 +02:00
}