1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

More even distribution of surface / underground zones + refactoring.

This commit is contained in:
DjWarmonger
2016-07-13 07:54:52 +02:00
parent ac68dca328
commit 1d8fb01f46
2 changed files with 171 additions and 127 deletions

View File

@@ -48,76 +48,36 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
{
logGlobal->infoStream() << "Starting zone placement";
int width = mapGenOptions->getWidth();
int height = mapGenOptions->getHeight();
width = mapGenOptions->getWidth();
height = mapGenOptions->getHeight();
auto zones = gen->getZones();
bool underground = mapGenOptions->getHasTwoLevels();
//gravity-based algorithm
/*
gravity-based algorithm
let's assume we try to fit N circular zones with radius = size on a map
*/
gravityConstant = 4e-3;
stiffnessConstant = 4e-3;
/*
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))
*/
std::vector<std::pair<TRmgTemplateZoneId, CRmgTemplateZone*>> zonesVector (zones.begin(), zones.end());
TZoneVector zonesVector(zones.begin(), zones.end());
assert (zonesVector.size());
RandomGeneratorUtil::randomShuffle(zonesVector, *rand);
TRmgTemplateZoneId firstZone = zones.begin()->first; //we want lowest ID here
bool undergroundFlag = false;
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;
for (auto zone : zonesVector)
{
//even distribution for surface / underground zones. Surface zones always have priority.
int level = 0;
if (underground) //only then consider underground zones
{
if (zone.first == firstZone)
{
level = 0;
}
else
{
level = undergroundFlag;
undergroundFlag = !undergroundFlag; //toggle underground on/off
}
}
totalSize[level] += (zone.second->getSize() * zone.second->getSize());
float randomAngle = rand->nextDouble(0, pi2);
zone.second->setCenter(float3(0.5f + std::sin(randomAngle) * radius, 0.5f + std::cos(randomAngle) * radius, level)); //place zones around circle
}
//prescale zones
std::vector<float> prescaler = { 0, 0 };
for (int i = 0; i < 2; i++)
prescaler[i] = sqrt((width * height) / (totalSize[i] * 3.14f));
mapSize = sqrt (width * height);
for (auto zone : zones)
{
zone.second->setSize (zone.second->getSize() * prescaler[zone.second->getCenter().z]);
}
//0. set zone sizes and surface / underground level
prepareZones(zones, zonesVector, underground, rand);
//gravity-based algorithm. connected zones attract, intersceting zones and map boundaries push back
//remember best solution
float bestTotalDistance = 1e10;
float bestTotalOverlap = 1e10;
//float bestRatio = 1e10;
std::map<CRmgTemplateZone *, float3> bestSolution;
const int maxDistanceMovementRatio = zones.size() * zones.size(); //experimental - the more zones, the greater total distance expected
std::map<CRmgTemplateZone *, float3> bestSolution;
TForceVector forces;
TForceVector totalForces; // both attraction and pushback, overcomplicated?
@@ -144,87 +104,15 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
}
//3. now perform drastic movement of zone that is completely not linked
float maxRatio = 0;
CRmgTemplateZone * misplacedZone = nullptr;
float totalDistance = 0;
float totalOverlap = 0;
for (auto zone : distances) //find most misplaced zone
{
totalDistance += zone.second;
float overlap = overlaps[zone.first];
totalOverlap += overlap;
float ratio = (zone.second + overlap) / totalForces[zone.first].mag(); //if distance to actual movement is long, the zone is misplaced
if (ratio > maxRatio)
{
maxRatio = ratio;
misplacedZone = zone.first;
}
}
logGlobal->traceStream() << boost::format("Worst misplacement/movement ratio: %3.2f") % maxRatio;
if (maxRatio > maxDistanceMovementRatio)
{
CRmgTemplateZone * targetZone = nullptr;
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];
float distance = otherZone->getCenter().dist2dSQ(ourCenter);
if (distance > maxDistance)
{
maxDistance = distance;
targetZone = otherZone;
}
}
float3 vec = targetZone->getCenter() - ourCenter;
float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize;
logGlobal->traceStream() << boost::format("Trying to move zone %d %s towards %d %s. Old distance %f") %
misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxDistance;
logGlobal->traceStream() << boost::format("direction is %s") % vec();
misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
}
else
{
float maxOverlap = 0;
for (auto otherZone : zones)
{
float3 otherZoneCenter = otherZone.second->getCenter();
if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z)
continue;
float distance = otherZoneCenter.dist2dSQ(ourCenter);
if (distance > maxOverlap)
{
maxOverlap = distance;
targetZone = otherZone.second;
}
}
float3 vec = ourCenter - targetZone->getCenter();
float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize;
logGlobal->traceStream() << boost::format("Trying to move zone %d %s away from %d %s. Old distance %f") %
misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxOverlap;
logGlobal->traceStream() << boost::format("direction is %s") % vec();
misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated
logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
}
}
moveOneZone(zones, totalForces, distances, overlaps);
//4. NOW after everything was moved, re-evaluate zone positions
attractConnectedZones(zones, forces, distances);
separateOverlappingZones(zones, forces, overlaps);
totalDistance = 0;
totalOverlap = 0;
float totalDistance = 0;
float totalOverlap = 0;
for (auto zone : distances) //find most misplaced zone
{
totalDistance += zone.second;
@@ -264,6 +152,78 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
}
}
void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand)
{
TRmgTemplateZoneId firstZone = zones.begin()->first; //we want lowest ID here
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)
{
//TODO: place players depending on their factions
if (zone.first == firstZone)
{
zonesOnLevel[0]++;
levels[zone.first] = 0;
}
else
{
zonesToPlace.push_back(zone);
}
}
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());
float randomAngle = rand->nextDouble(0, pi2);
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));
mapSize = sqrt(width * height);
for (auto zone : zones)
{
zone.second->setSize(zone.second->getSize() * prescaler[zone.second->getCenter().z]);
}
}
void CZonePlacer::attractConnectedZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &distances)
{
for (auto zone : zones)
@@ -356,6 +316,85 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
}
}
void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDistanceVector &distances, TDistanceVector &overlaps)
{
float maxRatio = 0;
const int maxDistanceMovementRatio = zones.size() * zones.size(); //experimental - the more zones, the greater total distance expected
CRmgTemplateZone * misplacedZone = nullptr;
float totalDistance = 0;
float totalOverlap = 0;
for (auto zone : distances) //find most misplaced zone
{
totalDistance += zone.second;
float overlap = overlaps[zone.first];
totalOverlap += overlap;
float ratio = (zone.second + overlap) / totalForces[zone.first].mag(); //if distance to actual movement is long, the zone is misplaced
if (ratio > maxRatio)
{
maxRatio = ratio;
misplacedZone = zone.first;
}
}
logGlobal->traceStream() << boost::format("Worst misplacement/movement ratio: %3.2f") % maxRatio;
if (maxRatio > maxDistanceMovementRatio)
{
CRmgTemplateZone * targetZone = nullptr;
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];
float distance = otherZone->getCenter().dist2dSQ(ourCenter);
if (distance > maxDistance)
{
maxDistance = distance;
targetZone = otherZone;
}
}
float3 vec = targetZone->getCenter() - ourCenter;
float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize;
logGlobal->traceStream() << boost::format("Trying to move zone %d %s towards %d %s. Old distance %f") %
misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxDistance;
logGlobal->traceStream() << boost::format("direction is %s") % vec();
misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size
logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
}
else
{
float maxOverlap = 0;
for (auto otherZone : zones)
{
float3 otherZoneCenter = otherZone.second->getCenter();
if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z)
continue;
float distance = otherZoneCenter.dist2dSQ(ourCenter);
if (distance > maxOverlap)
{
maxOverlap = distance;
targetZone = otherZone.second;
}
}
float3 vec = ourCenter - targetZone->getCenter();
float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize;
logGlobal->traceStream() << boost::format("Trying to move zone %d %s away from %d %s. Old distance %f") %
misplacedZone->getId() % ourCenter() % targetZone->getId() % targetZone->getCenter()() % maxOverlap;
logGlobal->traceStream() << boost::format("direction is %s") % vec();
misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated
logGlobal->traceStream() << boost::format("New distance %f") % targetZone->getCenter().dist2d(misplacedZone->getCenter());
}
}
}
float CZonePlacer::metric (const int3 &A, const int3 &B) const
{
/*

View File

@@ -21,6 +21,7 @@ class CRandomGenerator;
class CRmgTemplateZone;
class CMapGenerator;
typedef std::vector<std::pair<TRmgTemplateZoneId, CRmgTemplateZone*>> TZoneVector;
typedef std::map <TRmgTemplateZoneId, CRmgTemplateZone*> TZoneMap;
typedef std::map <CRmgTemplateZone *, float3> TForceVector;
typedef std::map <CRmgTemplateZone *, float> TDistanceVector;
@@ -46,12 +47,16 @@ public:
float getDistance(float distance) const; //additional scaling without 0 divison
~CZonePlacer();
void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand);
void attractConnectedZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &distances);
void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps);
void moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDistanceVector &distances, TDistanceVector &overlaps);
void placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand);
void assignZones(const CMapGenOptions * mapGenOptions);
private:
int width;
int height;
//metric coefiicients
float scaleX;
float scaleY;