diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 6fcb2f534..18e150ee1 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -321,6 +321,7 @@ + diff --git a/lib/VCMI_lib.vcxproj.filters b/lib/VCMI_lib.vcxproj.filters index f16a9ec91..4650865c0 100644 --- a/lib/VCMI_lib.vcxproj.filters +++ b/lib/VCMI_lib.vcxproj.filters @@ -396,5 +396,8 @@ Header Files + + rmg + - + \ No newline at end of file diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 7a3330c7b..afb7c8040 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -12,6 +12,7 @@ #include "../filesystem/Filesystem.h" #include "CRmgTemplate.h" #include "CRmgTemplateZone.h" +#include "CZonePlacer.h" CMapGenerator::CMapGenerator(shared_ptr mapGenOptions, int randomSeed /*= std::time(nullptr)*/) : mapGenOptions(mapGenOptions), randomSeed(randomSeed) @@ -26,10 +27,10 @@ CMapGenerator::~CMapGenerator() std::unique_ptr CMapGenerator::generate() { - mapGenOptions->finalize(rand); + mapGenOptions->finalize(rand); - map = make_unique(); - editManager = map->getEditManager(); + map = make_unique(); + editManager = map->getEditManager(); try { editManager->getUndoManager().setUndoRedoLimit(0); @@ -144,13 +145,16 @@ void CMapGenerator::genZones() auto tmpl = mapGenOptions->getMapTemplate(); - auto zones = tmpl->getZones(); + zones = tmpl->getZones(); //copy from template (refactor?) int player_per_side = zones.size() > 4 ? 3 : 2; int zones_cnt = zones.size() > 4 ? 9 : 4; logGlobal->infoStream() << boost::format("Map size %d %d, players per side %d") % w % h % player_per_side; + CZonePlacer placer(this); + placer.placeZones(mapGenOptions, &rand); + int i = 0; int part_w = w/player_per_side; int part_h = h/player_per_side; @@ -192,4 +196,9 @@ void CMapGenerator::addHeaderInfo() map->description = getMapDescription(); map->difficulty = 1; addPlayerInfo(); +} + +std::map CMapGenerator::getZones() const +{ + return zones; } \ No newline at end of file diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index c91d9e95c..badbf267d 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -62,6 +62,8 @@ public: int randomSeed; CMapEditManager * editManager; + std::map getZones() const; + private: std::map zones; diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index 38af91529..ec91e6304 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -81,6 +81,16 @@ void CJsonRmgTemplateLoader::loadTemplates() connections.push_back(conn); } tpl->setConnections(connections); + { + auto zones = tpl->getZones(); + for (auto con : tpl->getConnections()) + { + auto idA = con.getZoneA()->getId(); + auto idB = con.getZoneB()->getId(); + zones[idA]->addConnection(idB); + zones[idB]->addConnection(idA); + } + } tpl->validate(); templates[tpl->getName()] = tpl; } diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index bcebe8ea9..26e76f12e 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -272,6 +272,26 @@ void CRmgTemplateZone::setTownTypeLikeZone(boost::optional v townTypeLikeZone = value; } +void CRmgTemplateZone::addConnection(TRmgTemplateZoneId otherZone) +{ + connections.push_back (otherZone); +} + +std::vector CRmgTemplateZone::getConnections() const +{ + return connections; +} +float3 CRmgTemplateZone::getCenter() const +{ + return center; +} +void CRmgTemplateZone::setCenter(float3 f) +{ + //limit boundaries to (0,1) square + center = float3 (std::min(std::max(f.x, 0.f), 1.f), std::min(std::max(f.y, 0.f), 1.f), f.z); +} + + bool CRmgTemplateZone::pointIsIn(int x, int y) { int i, j; @@ -316,21 +336,26 @@ void CRmgTemplateZone::setShape(std::vector shape) } } -int3 CRmgTemplateZone::getCenter() +int3 CRmgTemplateZone::getPos() { - si32 cx = 0; - si32 cy = 0; - si32 area = 0; - si32 sz = shape.size(); - //include last->first too - for(si32 i = 0, j = sz-1; i < sz; j = i++) { - si32 sf = (shape[i].x * shape[j].y - shape[j].x * shape[i].y); - cx += (shape[i].x + shape[j].x) * sf; - cy += (shape[i].y + shape[j].y) * sf; - area += sf; - } - area /= 2; - return int3(std::abs(cx/area/6), std::abs(cy/area/6), shape[0].z); + //si32 cx = 0; + //si32 cy = 0; + //si32 area = 0; + //si32 sz = shape.size(); + ////include last->first too + //for(si32 i = 0, j = sz-1; i < sz; j = i++) { + // si32 sf = (shape[i].x * shape[j].y - shape[j].x * shape[i].y); + // cx += (shape[i].x + shape[j].x) * sf; + // cy += (shape[i].y + shape[j].y) * sf; + // area += sf; + //} + //area /= 2; + //return int3(std::abs(cx/area/6), std::abs(cy/area/6), shape[0].z); + return pos; +} +void CRmgTemplateZone::setPos(int3 Pos) +{ + pos = Pos; } bool CRmgTemplateZone::fill(CMapGenerator* gen) @@ -356,7 +381,7 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen) town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); - placeObject(gen, town, getCenter()); + placeObject(gen, town, getPos()); logGlobal->infoStream() << "Placed object"; logGlobal->infoStream() << "Fill player info " << player_id; @@ -512,7 +537,7 @@ void CRmgTemplateZone::checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos) { - logGlobal->infoStream() << boost::format("Inserting object at %d %d") % pos.x % pos.y; + logGlobal->traceStream() << boost::format("Inserting object at %d %d") % pos.x % pos.y; checkAndPlaceObject (gen, object, pos); @@ -537,7 +562,7 @@ void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object, bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str) { - logGlobal->infoStream() << boost::format("Guard object at %d %d") % object->pos.x % object->pos.y; + logGlobal->traceStream() << boost::format("Guard object at %d %d") % object->pos.x % object->pos.y; int3 visitable = object->visitablePos(); std::vector tiles; for(int i = -1; i < 2; ++i) diff --git a/lib/rmg/CRmgTemplateZone.h b/lib/rmg/CRmgTemplateZone.h index 1f94f8ac9..0272c122f 100644 --- a/lib/rmg/CRmgTemplateZone.h +++ b/lib/rmg/CRmgTemplateZone.h @@ -13,6 +13,7 @@ #include "../GameConstants.h" #include "CMapGenerator.h" +#include "float3.h" class CMapgenerator; @@ -98,10 +99,20 @@ public: void setTerrainTypeLikeZone(boost::optional value); boost::optional getTownTypeLikeZone() const; void setTownTypeLikeZone(boost::optional value); + + float3 getCenter() const; + void setCenter(float3 f); + int3 getPos(); + void setPos(int3 pos); + void setShape(std::vector shape); bool fill(CMapGenerator* gen); + void addConnection(TRmgTemplateZoneId otherZone); + std::vector getConnections() const; + private: + //template info TRmgTemplateZoneId id; ETemplateZoneType::ETemplateZoneType type; int size; @@ -113,11 +124,17 @@ private: std::set terrainTypes; boost::optional terrainTypeLikeZone, townTypeLikeZone; - std::vector shape; - std::map tileinfo; + //content info + std::vector shape; //TODO: remove std::vector objects; - int3 getCenter(); + //placement info + int3 pos; + float3 center; + std::map tileinfo; //irregular area assined to zone + std::vector connections; //list of adjacent zones + std::map alreadyConnected; //TODO: allow multiple connections between two zones? + bool pointIsIn(int x, int y); bool findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos); void checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos); diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 43c4d85e5..52e58f2ad 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -10,16 +10,20 @@ */ #include "StdInc.h" +#include "../CRandomGenerator.h" #include "CZonePlacer.h" +#include "CRmgTemplateZone.h" #include "CZoneGraphGenerator.h" -CPlacedZone::CPlacedZone(const CRmgTemplateZone * zone)// : zone(zone) +class CRandomGenerator; + +CPlacedZone::CPlacedZone(const CRmgTemplateZone * zone) : zone(zone) { } -CZonePlacer::CZonePlacer()// : map(nullptr), gen(nullptr) +CZonePlacer::CZonePlacer(CMapGenerator * Gen) : gen(Gen) { } @@ -29,7 +33,85 @@ CZonePlacer::~CZonePlacer() } -void CZonePlacer::placeZones(CMap * map, unique_ptr graph, CRandomGenerator * gen) +int3 CZonePlacer::cords (float3 f) const { - + return int3(f.x * gen->map->width, f.y * gen->map->height, f.z); +} + +void CZonePlacer::placeZones(shared_ptr mapGenOptions, CRandomGenerator * rand) +{ + //some relaxation-simmulated annealing algorithm + + const int iterations = 5; + float temperature = 1; + const float temperatureModifier = 0.9; + + logGlobal->infoStream() << "Starting zone placement"; + + int width = mapGenOptions->getWidth(); + int height = mapGenOptions->getHeight(); + + auto zones = gen->getZones(); + + //TODO: consider underground zones + + float totalSize = 0; + for (auto zone : zones) + { + totalSize += zone.second->getSize(); + zone.second->setCenter (float3(rand->nextDouble(0,1), rand->nextDouble(0,1), 0)); + } + //prescale zones + float prescaler = sqrt (width * height / totalSize) / 3.14f; //let's assume we try to fit N circular zones with radius = size on a map + float mapSize = sqrt (width * height); + for (auto zone : zones) + { + zone.second->setSize (zone.second->getSize() * prescaler); + } + + for (int i = 0; i < iterations; ++i) + { + for (auto zone : zones) + { + //attract connected zones + for (auto con : zone.second->getConnections()) + { + auto otherZone = zones[con]; + float distance = zone.second->getCenter().dist2d (otherZone->getCenter()); + float minDistance = (zone.second->getSize() + otherZone->getSize())/mapSize; //scale down to (0,1) coordinates + if (distance > minDistance) + { + //attract our zone + float scaler = (distance - minDistance)/distance * temperature; //positive + auto positionVector = (otherZone->getCenter() - zone.second->getCenter()); //positive value + zone.second->setCenter (zone.second->getCenter() + positionVector * scaler); //positive movement + } + } + } + for (auto zone : zones) + { + //separate overlaping zones + for (auto otherZone : zones) + { + if (zone == otherZone) + continue; + + float distance = zone.second->getCenter().dist2d (otherZone.second->getCenter()); + float minDistance = (zone.second->getSize() + otherZone.second->getSize())/mapSize; + if (distance < minDistance) + { + //move our zone away + float scaler = (distance ? (distance - minDistance)/distance : 1) * temperature; //negative + auto positionVector = (otherZone.second->getCenter() - zone.second->getCenter()); //positive value + zone.second->setCenter (zone.second->getCenter() + positionVector * scaler); //negative movement + } + } + } + temperature *= temperatureModifier; + } + 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(); + } } diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 7ba721f26..648adb428 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -11,35 +11,41 @@ #pragma once +#include "CMapGenerator.h" +#include "../mapping/CMap.h" + +#include "float3.h" +#include "../int3.h" + class CZoneGraph; class CMap; class CRandomGenerator; class CRmgTemplateZone; +class CMapGenerator; class CPlacedZone { public: - explicit CPlacedZone(const CRmgTemplateZone * zone); + explicit CPlacedZone(const CRmgTemplateZone * Zone); private: - //const CRmgTemplateZone * zone; + const CRmgTemplateZone * zone; //TODO exact outline data of zone //TODO perhaps further zone data, guards, obstacles, etc... }; -//TODO add voronoi helper classes(?), etc... - class CZonePlacer { public: - CZonePlacer(); + explicit CZonePlacer(CMapGenerator * gen); + int3 cords (float3 f) const; ~CZonePlacer(); - void placeZones(CMap * map, unique_ptr graph, CRandomGenerator * gen); + void placeZones(shared_ptr mapGenOptions, CRandomGenerator * rand); private: //CMap * map; - unique_ptr graph; - //CRandomGenerator * gen; + //unique_ptr graph; + CMapGenerator * gen; }; diff --git a/lib/rmg/float3.h b/lib/rmg/float3.h new file mode 100644 index 000000000..36f6feba0 --- /dev/null +++ b/lib/rmg/float3.h @@ -0,0 +1,132 @@ +#pragma once + +/* + * float3.h, 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 + * + */ + +/// Class which consists of three float values. Represents position virtual RMG (0;1) area. +class float3 +{ +public: + float x, y; + si32 z; + inline float3():x(0),y(0),z(0){}; //c-tor, x/y/z initialized to 0 + inline float3(const float X, const float Y, const si32 Z):x(X),y(Y),z(Z){}; //c-tor + inline float3(const float3 & val) : x(val.x), y(val.y), z(val.z){} //copy c-tor + inline float3 & operator=(const float3 & val) {x = val.x; y = val.y; z = val.z; return *this;} //assignemt operator + ~float3() {} // d-tor - does nothing + inline float3 operator+(const float3 & i) const //returns float3 with coordinates increased by corresponding coordinate of given float3 + {return float3(x+i.x,y+i.y,z+i.z);} + inline float3 operator+(const float i) const //returns float3 with coordinates increased by given numer + {return float3(x+i,y+i,z+i);} + inline float3 operator-(const float3 & i) const //returns float3 with coordinates decreased by corresponding coordinate of given float3 + {return float3(x-i.x,y-i.y,z-i.z);} + inline float3 operator-(const float i) const //returns float3 with coordinates decreased by given numer + {return float3(x-i,y-i,z-i);} + inline float3 operator*(const float i) const //returns float3 with plane coordinates decreased by given numer + {return float3(x*i, y*i, z);} + inline float3 operator/(const float i) const //returns float3 with plane coordinates decreased by given numer + {return float3(x/i, y/i, z);} + inline float3 operator-() const //returns opposite position + {return float3(-x,-y,-z);} + inline double dist2d(const float3 &other) const //distance (z coord is not used) + {return std::sqrt((double)(x-other.x)*(x-other.x) + (y-other.y)*(y-other.y));} + inline bool areNeighbours(const float3 &other) const + {return dist2d(other) < 2. && z == other.z;} + inline void operator+=(const float3 & i) + { + x+=i.x; + y+=i.y; + z+=i.z; + } + inline void operator+=(const float & i) + { + x+=i; + y+=i; + z+=i; + } + inline void operator-=(const float3 & i) + { + x-=i.x; + y-=i.y; + z-=i.z; + } + inline void operator-=(const float & i) + { + x+=i; + y+=i; + z+=i; + } + inline void operator*=(const float & i) //scale on plane + { + x*=i; + y*=i; + } + inline void operator/=(const float & i) //scale on plane + { + x/=i; + y/=i; + } + + inline bool operator==(const float3 & i) const + {return (x==i.x) && (y==i.y) && (z==i.z);} + inline bool operator!=(const float3 & i) const + {return !(*this==i);} + inline bool operator<(const float3 & i) const + { + if (zi.z) + return false; + if (yi.y) + return false; + if (xi.x) + return false; + return false; + } + inline std::string operator ()() const + { + return "(" + boost::lexical_cast(x) + + " " + boost::lexical_cast(y) + + " " + boost::lexical_cast(z) + ")"; + } + inline bool valid() const + { + return z >= 0; //minimal condition that needs to be fulfilled for tiles in the map + } + template void serialize(Handler &h, const float version) + { + h & x & y & z; + } + +}; +inline std::istream & operator>>(std::istream & str, float3 & dest) +{ + str>>dest.x>>dest.y>>dest.z; + return str; +} +inline std::ostream & operator<<(std::ostream & str, const float3 & sth) +{ + return str<()(pos.x); + vstd::hash_combine(ret, pos.y); + vstd::hash_combine(ret, pos.z); + return ret; + } +};