From 09d50a5e9fd0ecfdf5a184a9b18a06d4a36c1ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 09:02:58 +0200 Subject: [PATCH 01/19] First draft that kinda works. (cherry picked from commit 7dd5a9c15dd32d3d9248c8baa8231932891a163c) --- lib/rmg/CZonePlacer.cpp | 310 ++++++++++++++++++++++++++++++++++++++++ lib/rmg/CZonePlacer.h | 5 + 2 files changed, 315 insertions(+) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 4551c6262..a71da5528 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" +#include #include "../CRandomGenerator.h" #include "CZonePlacer.h" #include "../TerrainHandler.h" @@ -40,6 +41,312 @@ float CZonePlacer::getDistance (float distance) const return (distance ? distance * distance : 1e-6f); } +void CZonePlacer::findPathsBetweenZones() +{ + typedef std::pair ConnectionIndex; + + auto zones = map.getZones(); + + std::set> zonesToCheck; + + //Initialize direct connections + for (auto zone : zones) + { + auto zoneId = zone.second->getId(); + for (auto connection : zone.second->getConnections()) + { + if (!vstd::contains(distancesBetweenZones[zoneId], connection)) + { + distancesBetweenZones[zoneId][connection] = 1; + distancesBetweenZones[connection][zoneId] = 1; + } + } + } + + for (auto startZone : zones) + { + size_t start = startZone.second->getId(); + + for (auto endZone : zones) + { + size_t end = endZone.second->getId(); + + if (start != end) + { + auto currentEnd = end; + while (!vstd::contains(distancesBetweenZones[start], end)) + { + size_t distance = 10; //Some large but not infinite number to not blow up the weights + std::stack nearbyZones; + std::set checkedZones; + + //FIXME: we may know the path from previous iterations, but can't be sure if it's optimal :? + + for (auto nearbyZone : startZone.second->getConnections()) + { + nearbyZones.push(nearbyZone); + } + + while (!nearbyZones.empty()) + { + auto currentZone = nearbyZones.top(); + nearbyZones.pop(); + + checkedZones.insert(currentZone); + + for (auto neighbourZone : distancesBetweenZones[currentZone]) + { + if (neighbourZone.first == currentEnd) + { + //This zone has connection to our end zone + + if (!vstd::contains(distancesBetweenZones[currentZone], currentEnd)) + { + //Initialize the connection of adjacent zones + distancesBetweenZones[currentZone][currentEnd] = 1; + } + + if ((distancesBetweenZones[currentZone][currentEnd] + 1) < distance) + { + //We found new, shorter path + distance = distancesBetweenZones[currentZone][currentEnd] + 1; + + //Add just found connection + distancesBetweenZones[start][currentEnd] = distance; + //Connection is bidirectional + distancesBetweenZones[currentEnd][start] = distance; + + //Unwind the stack, find the path between start previous-to-last zone + currentEnd = currentZone; + } + } + else + { + if (!vstd::contains(checkedZones, neighbourZone.first)) + { + //We didn't check that zone yet + nearbyZones.push(neighbourZone.first); + } + } + } + } + //At the very least after this step we will find 1 more step connecting the two zones + } + } + } + } + + //Dump debug + for (auto startZone : zones) + { + auto startId = startZone.second->getId(); + + for (auto endZone : zones) + { + auto endId = endZone.second->getId(); + + if (startId >= endId) + { + //Print only conections in one way + continue; + } + logGlobal->info((boost::format("Distance between zone %2d and %2d: %d") + % startId % endId % distancesBetweenZones[startId][endId]).str()); + } + } +} + +void CZonePlacer::placeOnGrid(CRandomGenerator* rand) +{ + auto zones = map.getZones(); + assert(zones.size()); + + //TODO: determine all the distances between zones on a graph + + //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, 2> GridType; + GridType grid(boost::extents[gridSize][gridSize]); + + TZoneVector zonesVector(zones.begin(), zones.end()); + RandomGeneratorUtil::randomShuffle(zonesVector, *rand); + + //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: + if (firstZone->getConnections().size() > 2) + { + 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: + if (gridSize && 1) //odd + { + 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; + auto connections = zone->getConnections(); + + float maxDistance = 0.0; + 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 + for (size_t existingX = 0; existingX < gridSize; ++existingX) + { + for (size_t existingY = 0; existingY < gridSize; ++existingY) + { + float distance = 0.0; + auto existingZone = grid[existingX][existingY]; + if (existingZone ) + { + //There is already zone here + + if (distancesBetweenZones[zone->getId()][existingZone->getId()] > 1) + { + //No direct connection + distance += potentialPos.dist2d(int3(existingX, existingY, 0)); + //TODO: Multiply by weight - the distance from A* + } + else + { + //Has direct connection + distance -= (gridSize - 1); + } + + //TODO: Multiply if zones belong to players, especially humans. + //Starting zones should be as far away from eahc other as possible + + if (distance > maxDistance) + { + distance = maxDistance; + mostDistantPlace = potentialPos; + } + } + } + } + } + } + } + + //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) + { + auto targetX = rand->nextDouble(x - 0.5f, x + 0.5f); + vstd::clamp(targetX, 0, gridSize); + auto targetY = rand->nextDouble(y - 0.5f, y + 0.5f); + vstd::clamp(targetY, 0, gridSize); + + zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); + } + } + } +} + void CZonePlacer::placeZones(CRandomGenerator * rand) { logGlobal->info("Starting zone placement"); @@ -54,6 +361,9 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) }); bool underground = map.getMapGenOptions().getHasTwoLevels(); + findPathsBetweenZones(); + placeOnGrid(rand); + /* gravity-based algorithm diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 0e2002241..f326e86a0 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -37,6 +37,8 @@ public: ~CZonePlacer() = default; void placeZones(CRandomGenerator * rand); + void findPathsBetweenZones(); + void placeOnGrid(CRandomGenerator* rand); void assignZones(CRandomGenerator * rand); private: @@ -58,6 +60,9 @@ private: //float a1, b1, c1, a2, b2, c2; //CMap * map; //std::unique_ptr graph; + + //distance [a][b] = number of zone connections required to travel between the zones + std::map> distancesBetweenZones; RmgMap & map; }; From 64adc9983d24a92bbd0e113456141de3ee62281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 09:43:58 +0200 Subject: [PATCH 02/19] - Fixes for corectness - Space apart starting zones of players (cherry picked from commit 80be12ac682129ef24864d9639ecb27d00a6c398) --- lib/rmg/CZonePlacer.cpp | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index a71da5528..6f5a348f8 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -161,8 +161,6 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) auto zones = map.getZones(); assert(zones.size()); - //TODO: determine all the distances between zones on a graph - //Make sure there are at least as many grid fields as the number of zones size_t gridSize = std::ceil(std::sqrt(zones.size())); @@ -254,7 +252,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) auto zone = zonesVector[i].second; auto connections = zone->getConnections(); - float maxDistance = 0.0; + float maxDistance = -1000.0; int3 mostDistantPlace; //Iterate over free positions @@ -268,30 +266,46 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) int3 potentialPos(freeX, freeY, 0); //Compute distance to every existing zone + + float distance = 0; for (size_t existingX = 0; existingX < gridSize; ++existingX) { for (size_t existingY = 0; existingY < gridSize; ++existingY) { - float distance = 0.0; auto existingZone = grid[existingX][existingY]; - if (existingZone ) + if (existingZone) { //There is already zone here + float localDistance = 0.0f; - if (distancesBetweenZones[zone->getId()][existingZone->getId()] > 1) + auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; + if (graphDistance > 1) { //No direct connection - distance += potentialPos.dist2d(int3(existingX, existingY, 0)); - //TODO: Multiply by weight - the distance from A* + localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; } else { - //Has direct connection - distance -= (gridSize - 1); + //Has direct connection - place as close as possible + localDistance = -(gridSize - 1); } - //TODO: Multiply if zones belong to players, especially humans. - //Starting zones should be as far away from eahc other as possible + //Spread apart player starting zones + + auto zoneType = zone->getType(); + auto existingZoneType = existingZone->getType(); + if ((zoneType == ETemplateZoneType::PLAYER_START || zoneType == ETemplateZoneType::CPU_START) && + (existingZoneType == ETemplateZoneType::PLAYER_START || existingZoneType == ETemplateZoneType::CPU_START)) + { + int firstPlayer = zone->getOwner().get(); + int secondPlayer = existingZone->getOwner().get(); + + //Players with lower indexes (especially 1 and 2) will be placed further apart + + localDistance *= (1.0f + (std::abs(firstPlayer - secondPlayer) / (firstPlayer * secondPlayer))); + } + + distance += localDistance; if (distance > maxDistance) { From 3f83eaafc4a7a050c6936351bc4773306b55e163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 10:00:24 +0200 Subject: [PATCH 03/19] Fix distance check (cherry picked from commit 319f289dc4acbb43c366c06b12f5bc64c54c593f) --- lib/rmg/CZonePlacer.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 6f5a348f8..e43e6374f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -306,15 +306,14 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) } distance += localDistance; - - if (distance > maxDistance) - { - distance = maxDistance; - mostDistantPlace = potentialPos; - } } } } + if (distance > maxDistance) + { + maxDistance = distance; + mostDistantPlace = potentialPos; + } } } } From 48c11f661b3adb151b8d2b1db2e04115c19e8e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 11:11:35 +0200 Subject: [PATCH 04/19] Fix zone attraction (cherry picked from commit 164ecaea606898d1ec6a4bb0db34ecdef1090538) --- lib/rmg/CZonePlacer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index e43e6374f..d1c644e54 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -287,7 +287,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) else { //Has direct connection - place as close as possible - localDistance = -(gridSize - 1); + localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); } //Spread apart player starting zones From 7d2745fda6e2e8062e17a78b996c1a5c9c8dc5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 17:59:59 +0200 Subject: [PATCH 05/19] Simplified target function comparison. (cherry picked from commit 809f6344ffc0a0b12b2e08bef1a44cbb98369f1d) --- lib/rmg/CZonePlacer.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index d1c644e54..fe72425e1 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -445,15 +445,10 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) //check fitness function bool improvement = false; - if (bestTotalDistance > 0 && bestTotalOverlap > 0) + if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) { - if (totalDistance * totalOverlap < bestTotalDistance * bestTotalOverlap) //multiplication is better for auto-scaling, but stops working if one factor is 0 - improvement = true; - } - else - { - if (totalDistance + totalOverlap < bestTotalDistance + bestTotalOverlap) - improvement = true; + //multiplication is better for auto-scaling, but stops working if one factor is 0 + improvement = true; } logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); From a1f094776ffee34ec2abda96c44db472c400697d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 10 Apr 2023 18:00:23 +0200 Subject: [PATCH 06/19] Use fixed starting positions for now. (cherry picked from commit 43c51805f5a86cf2589251d3e36b6bdef3f0626f) --- lib/rmg/CZonePlacer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index fe72425e1..8c185a10e 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -349,10 +349,11 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) auto zone = grid[x][y]; if (zone) { - auto targetX = rand->nextDouble(x - 0.5f, x + 0.5f); - vstd::clamp(targetX, 0, gridSize); - auto targetY = rand->nextDouble(y - 0.5f, y + 0.5f); - vstd::clamp(targetY, 0, gridSize); + //i.e. for grid size 5 we get range (0.5 - 4.5) + auto targetX = rand->nextDouble(x + 0.5f, x + 0.5f); + vstd::clamp(targetX, 0.5, gridSize - 0.5); + auto targetY = rand->nextDouble(y + 0.5f, y + 0.5f); + vstd::clamp(targetY, 0.5, gridSize - 0.5); zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); } From 494f4eaa3fa2a37a8a759e90df3b55c1b2eec3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 16 Apr 2023 20:24:42 +0200 Subject: [PATCH 07/19] Simple solution that works - by ChatGPT :) (cherry picked from commit 7c6e4bc2fe37c716097ff94a6a589db2e87496bf) --- lib/rmg/CZonePlacer.cpp | 101 +++++++++------------------------------- 1 file changed, 23 insertions(+), 78 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 8c185a10e..96942df45 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -43,94 +43,39 @@ float CZonePlacer::getDistance (float distance) const void CZonePlacer::findPathsBetweenZones() { - typedef std::pair ConnectionIndex; - auto zones = map.getZones(); std::set> zonesToCheck; - //Initialize direct connections - for (auto zone : zones) - { - auto zoneId = zone.second->getId(); - for (auto connection : zone.second->getConnections()) - { - if (!vstd::contains(distancesBetweenZones[zoneId], connection)) - { - distancesBetweenZones[zoneId][connection] = 1; - distancesBetweenZones[connection][zoneId] = 1; - } - } - } + // Iterate through each pair of nodes in the graph - for (auto startZone : zones) + for (const auto& zone : zones) { - size_t start = startZone.second->getId(); + int start = zone.first; + const auto& zone1Ptr = zone.second; + distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 - for (auto endZone : zones) + std::queue q; + std::map visited; + visited[start] = true; + q.push(start); + + // Perform Breadth-First Search from the starting node + while (!q.empty()) { - size_t end = endZone.second->getId(); - - if (start != end) + int current = q.front(); + q.pop(); + + const auto& currentZone = zones.at(current); + const auto& connections = currentZone->getConnections(); + + for (uint32_t neighbor : connections) { - auto currentEnd = end; - while (!vstd::contains(distancesBetweenZones[start], end)) + if (!visited[neighbor]) { - size_t distance = 10; //Some large but not infinite number to not blow up the weights - std::stack nearbyZones; - std::set checkedZones; - - //FIXME: we may know the path from previous iterations, but can't be sure if it's optimal :? - - for (auto nearbyZone : startZone.second->getConnections()) - { - nearbyZones.push(nearbyZone); - } - - while (!nearbyZones.empty()) - { - auto currentZone = nearbyZones.top(); - nearbyZones.pop(); - - checkedZones.insert(currentZone); - - for (auto neighbourZone : distancesBetweenZones[currentZone]) - { - if (neighbourZone.first == currentEnd) - { - //This zone has connection to our end zone - - if (!vstd::contains(distancesBetweenZones[currentZone], currentEnd)) - { - //Initialize the connection of adjacent zones - distancesBetweenZones[currentZone][currentEnd] = 1; - } - - if ((distancesBetweenZones[currentZone][currentEnd] + 1) < distance) - { - //We found new, shorter path - distance = distancesBetweenZones[currentZone][currentEnd] + 1; - - //Add just found connection - distancesBetweenZones[start][currentEnd] = distance; - //Connection is bidirectional - distancesBetweenZones[currentEnd][start] = distance; - - //Unwind the stack, find the path between start previous-to-last zone - currentEnd = currentZone; - } - } - else - { - if (!vstd::contains(checkedZones, neighbourZone.first)) - { - //We didn't check that zone yet - nearbyZones.push(neighbourZone.first); - } - } - } - } - //At the very least after this step we will find 1 more step connecting the two zones + visited[neighbor] = true; + q.push(neighbor); + distancesBetweenZones[start][neighbor] = distancesBetweenZones[start][current] + 1; } } } From 2d7a8199399eb551930f64093862bde29a0b9eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 16 Apr 2023 21:12:21 +0200 Subject: [PATCH 08/19] Parameters which work well for Jebus - accessible desert and Blue placed away from Red (cherry picked from commit 42c6127c79c3a4b75122aec85e58b32df860609c) --- lib/rmg/CZonePlacer.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 96942df45..d60979cad 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -247,7 +247,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) //Players with lower indexes (especially 1 and 2) will be placed further apart - localDistance *= (1.0f + (std::abs(firstPlayer - secondPlayer) / (firstPlayer * secondPlayer))); + localDistance *= (1.0f + (2.0f / (firstPlayer * secondPlayer))); } distance += localDistance; @@ -329,8 +329,8 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) let's assume we try to fit N circular zones with radius = size on a map */ - gravityConstant = 4e-3f; - stiffnessConstant = 4e-3f; + gravityConstant = 2e-3f; + stiffnessConstant = 6e-3f; TZoneVector zonesVector(zones.begin(), zones.end()); assert (zonesVector.size()); @@ -579,7 +579,9 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; if (distance < minDistance) { - forceVector -= (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stiffnessConstant; //negative value + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stiffnessConstant; + //negative value + forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); overlap += (minDistance - distance); //overlapping of small zones hurts us more } } @@ -630,7 +632,8 @@ void CZonePlacer::moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDis totalDistance += zone.second; float overlap = overlaps[zone.first]; totalOverlap += overlap; - float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); //if distance to actual movement is long, the zone is misplaced + //if distance to actual movement is long, the zone is misplaced + float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); if (ratio > maxRatio) { maxRatio = ratio; From fc91152da732924baa10c70195c116f6a29a519e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 17 Apr 2023 14:56:35 +0200 Subject: [PATCH 09/19] Fixed zones getting actually randomized after construction. Tweaks to algorithm, now it's considerably better. (cherry picked from commit c13019059fafa56665eefb39c9ec3851126cf2cf) --- lib/rmg/CZonePlacer.cpp | 52 ++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index d60979cad..37b8c8320 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -80,25 +80,6 @@ void CZonePlacer::findPathsBetweenZones() } } } - - //Dump debug - for (auto startZone : zones) - { - auto startId = startZone.second->getId(); - - for (auto endZone : zones) - { - auto endId = endZone.second->getId(); - - if (startId >= endId) - { - //Print only conections in one way - continue; - } - logGlobal->info((boost::format("Distance between zone %2d and %2d: %d") - % startId % endId % distancesBetweenZones[startId][endId]).str()); - } - } } void CZonePlacer::placeOnGrid(CRandomGenerator* rand) @@ -296,9 +277,9 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) { //i.e. for grid size 5 we get range (0.5 - 4.5) auto targetX = rand->nextDouble(x + 0.5f, x + 0.5f); - vstd::clamp(targetX, 0.5, gridSize - 0.5); + std::clamp(targetX, 0.5, gridSize - 0.5); auto targetY = rand->nextDouble(y + 0.5f, y + 0.5f); - vstd::clamp(targetY, 0.5, gridSize - 0.5); + std::clamp(targetY, 0.5, gridSize - 0.5); zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); } @@ -324,13 +305,14 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) placeOnGrid(rand); /* - gravity-based algorithm + Fruchterman-Reingold algorithm - let's assume we try to fit N circular zones with radius = size on a map + 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 */ - gravityConstant = 2e-3f; - stiffnessConstant = 6e-3f; + gravityConstant = 5e-4f; + stiffnessConstant = 3e-3f; TZoneVector zonesVector(zones.begin(), zones.end()); assert (zonesVector.size()); @@ -340,8 +322,6 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) //0. set zone sizes and surface / underground level prepareZones(zones, zonesVector, underground, rand); - //gravity-based algorithm. connected zones attract, intersecting zones and map boundaries push back - //remember best solution float bestTotalDistance = 1e10; float bestTotalOverlap = 1e10; @@ -499,12 +479,14 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const else levels[zone.first] = 0; } + for(const auto & zone : zonesVector) { int level = levels[zone.first]; totalSize[level] += (zone.second->getSize() * zone.second->getSize()); - auto randomAngle = static_cast(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 + float3 center = zone.second->getCenter(); + center.z = level; + zone.second->setCenter(center); } /* @@ -538,6 +520,11 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, auto otherZone = zones[con]; float3 otherZoneCenter = otherZone->getCenter(); auto distance = static_cast(pos.dist2d(otherZoneCenter)); + + forceVector += (otherZoneCenter - pos) * distance * gravityConstant; //positive value + + //Attract zone centers always + float minDistance = 0; if (pos.z != otherZoneCenter.z) @@ -546,12 +533,7 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, 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; - forceVector += ((otherZoneCenter - pos)* overlapMultiplier / getDistance(distance)) * gravityConstant; //positive value totalDistance += (distance - minDistance); - } } distances[zone.second] = totalDistance; forceVector.z = 0; //operator - doesn't preserve z coordinate :/ @@ -622,7 +604,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces void CZonePlacer::moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps) const { float maxRatio = 0; - const int maxDistanceMovementRatio = static_cast(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected + const int maxDistanceMovementRatio = 1e1 * static_cast(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected std::shared_ptr misplacedZone; float totalDistance = 0; From 8d5cd6d072b4eae28f8570578c919a826ddbd661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 17 Apr 2023 15:09:08 +0200 Subject: [PATCH 10/19] Randomize starting positions a bit so zones don't fall exactly on the grid. (cherry picked from commit 33eb28b570e132f63d7aaa451a732f3d0c9a10d6) --- lib/rmg/CZonePlacer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 37b8c8320..66ff1491c 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -276,10 +276,10 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) if (zone) { //i.e. for grid size 5 we get range (0.5 - 4.5) - auto targetX = rand->nextDouble(x + 0.5f, x + 0.5f); - std::clamp(targetX, 0.5, gridSize - 0.5); - auto targetY = rand->nextDouble(y + 0.5f, y + 0.5f); - std::clamp(targetY, 0.5, gridSize - 0.5); + 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); zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); } From 6551585f966d7bd68d857c7ca24c86c8f4b3c195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 17 Apr 2023 16:37:24 +0200 Subject: [PATCH 11/19] Start with low stifness to let zones pass through each other - typical temperature fall. (cherry picked from commit 435b9f78819b7bd282fc45ac8297ca12aaec5c87) --- lib/rmg/CZonePlacer.cpp | 18 +++++++++--------- lib/rmg/CZonePlacer.h | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 66ff1491c..9e22204dc 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -25,10 +25,13 @@ VCMI_LIB_NAMESPACE_BEGIN class CRandomGenerator; CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), scaleX(0), scaleY(0), mapSize(0), gravityConstant(0), stiffnessConstant(0), + : width(0), height(0), scaleX(0), scaleY(0), mapSize(0), + gravityConstant(5e-4f), + stiffnessConstant(3e-3f), + stifness(0), + stiffnessIncreaseFactor(1.05f), map(map) { - } int3 CZonePlacer::cords(const float3 & f) const @@ -311,9 +314,6 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) Connected zones attract, intersecting zones and map boundaries push back */ - gravityConstant = 5e-4f; - stiffnessConstant = 3e-3f; - TZoneVector zonesVector(zones.begin(), zones.end()); assert (zonesVector.size()); @@ -333,8 +333,8 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) TDistanceVector distances; TDistanceVector overlaps; - const int MAX_ITERATIONS = 100; - for (int i = 0; i < MAX_ITERATIONS; ++i) //until zones reach their desired size and fill the map tightly + //Start with low stiffness. Bigger graphs need more time and more flexibility + for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant; stifness *= stiffnessIncreaseFactor) { //1. attract connected zones attractConnectedZones(zones, forces, distances); @@ -561,7 +561,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; if (distance < minDistance) { - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stiffnessConstant; + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; //negative value forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); overlap += (minDistance - distance); //overlapping of small zones hurts us more @@ -577,7 +577,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces float3 boundary = float3(x, y, pos.z); auto distance = static_cast(pos.dist2d(boundary)); overlap += std::max(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 + forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value }; if (pos.x < size) { diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index f326e86a0..4750223a0 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -57,6 +57,8 @@ private: float gravityConstant; float stiffnessConstant; + float stifness; + float stiffnessIncreaseFactor; //float a1, b1, c1, a2, b2, c2; //CMap * map; //std::unique_ptr graph; From 39154737a5a70e66fe9cd960bfa42207b1ef6477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 18 Apr 2023 19:34:54 +0200 Subject: [PATCH 12/19] Use std::optional instead of boost::optional. --- lib/rmg/CZonePlacer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 9e22204dc..716cf869f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -226,8 +226,8 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) if ((zoneType == ETemplateZoneType::PLAYER_START || zoneType == ETemplateZoneType::CPU_START) && (existingZoneType == ETemplateZoneType::PLAYER_START || existingZoneType == ETemplateZoneType::CPU_START)) { - int firstPlayer = zone->getOwner().get(); - int secondPlayer = existingZone->getOwner().get(); + int firstPlayer = zone->getOwner().value(); + int secondPlayer = existingZone->getOwner().value(); //Players with lower indexes (especially 1 and 2) will be placed further apart From 15592c3aff05d28a5eeaad4323ced3d34cdd426b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 18 Apr 2023 22:01:51 +0200 Subject: [PATCH 13/19] Change metric to create more interesting zone shapes. --- lib/rmg/CZonePlacer.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 716cf869f..5890d4613 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -687,22 +687,18 @@ void CZonePlacer::moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDis float CZonePlacer::metric (const int3 &A, const int3 &B) const { -/* - -Matlab code - - dx = abs(A(1) - B(1)); %distance must be symmetric - dy = abs(A(2) - B(2)); - -d = 0.01 * dx^3 - 0.1618 * dx^2 + 1 * dx + ... - 0.01618 * dy^3 + 0.1 * dy^2 + 0.168 * dy; -*/ - float dx = abs(A.x - B.x) * scaleX; float dy = abs(A.y - B.y) * scaleY; - //Horner scheme - return dx * (1.0f + dx * (0.1f + dx * 0.01f)) + dy * (1.618f + dy * (-0.1618f + dy * 0.01618f)); + /* + 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) + + 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 50); } void CZonePlacer::assignZones(CRandomGenerator * rand) From d841655c1dd48cde3ca096e2d02674e15b272146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 18 Apr 2023 22:34:26 +0200 Subject: [PATCH 14/19] Scale fuzzy edges with map size. --- lib/rmg/CZonePlacer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 5890d4613..e7c1ebb0c 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -698,7 +698,7 @@ float CZonePlacer::metric (const int3 &A, const int3 &B) const return dx * dx + dy * dy + 5 * std::sin(dx * dy / 10) + - 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 50); + 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY)); } void CZonePlacer::assignZones(CRandomGenerator * rand) From c34b1cd71362a2e948d0de162b5efd7eb3b3a8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 19 Apr 2023 08:45:23 +0200 Subject: [PATCH 15/19] Fix warning-as-errors. --- lib/rmg/CZonePlacer.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index e7c1ebb0c..52cacf438 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -55,7 +55,6 @@ void CZonePlacer::findPathsBetweenZones() for (const auto& zone : zones) { int start = zone.first; - const auto& zone1Ptr = zone.second; distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 std::queue q; @@ -157,7 +156,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) } break; case ETemplateZoneType::TREASURE: - if (gridSize && 1) //odd + if (gridSize & 1) //odd { x = y = (gridSize / 2); } @@ -402,9 +401,6 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const { std::vector 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. From 00d7901e594181fab070cd3906aaf8a1a5817c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 20 Apr 2023 12:24:57 +0200 Subject: [PATCH 16/19] Add another placement technique - swap two misplaced zones. Don't move same zones in consecutive iterations. --- lib/rmg/CZonePlacer.cpp | 169 ++++++++++++++++++++++++++-------------- lib/rmg/CZonePlacer.h | 3 +- 2 files changed, 113 insertions(+), 59 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 52cacf438..466dc2637 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -277,7 +277,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) auto zone = grid[x][y]; if (zone) { - //i.e. for grid size 5 we get range (0.5 - 4.5) + //i.e. for grid size 5 we get range (0.25 - 4.75) 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); @@ -597,88 +597,141 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces } } -void CZonePlacer::moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps) const +void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) { - float maxRatio = 0; - const int maxDistanceMovementRatio = 1e1 * static_cast(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected - std::shared_ptr misplacedZone; + const int maxDistanceMovementRatio = zones.size() * zones.size(); //The more zones, the greater total distance expected + + typedef std::pair> Misplacement; + std::vector misplacedZones; float totalDistance = 0; float totalOverlap = 0; - for(const auto & zone : distances) //find most misplaced zone + for (const auto& zone : distances) //find most misplaced zone { + if (vstd::contains(lastSwappedZones, zone.first->getId())) + { + continue; + } totalDistance += zone.second; float overlap = overlaps[zone.first]; totalOverlap += overlap; //if distance to actual movement is long, the zone is misplaced float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); - if (ratio > maxRatio) + if (ratio > maxDistanceMovementRatio) { - maxRatio = ratio; - misplacedZone = zone.first; + misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); } } - logGlobal->trace("Worst misplacement/movement ratio: %3.2f", maxRatio); - if (maxRatio > maxDistanceMovementRatio && misplacedZone) + if (misplacedZones.empty()) + return; + + boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) { - std::shared_ptr targetZone; - float3 ourCenter = misplacedZone->getCenter(); + return lhs.first > rhs.first; //Biggest first + }); - if (totalDistance > totalOverlap) + logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); + + if (misplacedZones.size() >= 2) + { + //Swap 2 misplaced zones + + auto firstZone = misplacedZones.front().second; + std::shared_ptr secondZone; + + auto level = firstZone->getCenter().z; + for (size_t i = 1; i < misplacedZones.size(); i++) { - //find most distant zone that should be attracted and move inside it - float maxDistance = 0; - for (auto con : misplacedZone->getConnections()) + //Only swap zones on the same level + //Don't swap zones that should be connected (Jebus) + if (misplacedZones[i].second->getCenter().z == level && + !vstd::contains(firstZone->getConnections(), misplacedZones[i].second->getId())) { - auto otherZone = zones[con]; - float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); - if (distance > maxDistance) - { - maxDistance = distance; - targetZone = otherZone; - } - } - if (targetZone) //TODO: consider refactoring duplicated code - { - float3 vec = targetZone->getCenter() - ourCenter; - float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; - 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()); - - misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size - logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); + secondZone = misplacedZones[i].second; + break; } } - else + if (secondZone) { - float maxOverlap = 0; - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); + logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); - if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) - continue; + auto firstCenter = firstZone->getCenter(); + auto secondCenter = secondZone->getCenter(); + firstZone->setCenter(secondCenter); + secondZone->setCenter(firstCenter); - auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); - if (distance > maxOverlap) - { - maxOverlap = distance; - targetZone = otherZone.second; - } - } - if (targetZone) - { - float3 vec = ourCenter - targetZone->getCenter(); - float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; - 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()); - - misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated - logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); - } + lastSwappedZones.insert(firstZone->getId()); + lastSwappedZones.insert(secondZone->getId()); + return; } } + 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 targetZone; + auto misplacedZone = misplacedZones.front().second; + float3 ourCenter = misplacedZone->getCenter(); + + if (totalDistance > totalOverlap) + { + //Move one zone towards most distant zone to reduce distance + + float maxDistance = 0; + for (auto con : misplacedZone->getConnections()) + { + auto otherZone = zones[con]; + float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); + if (distance > maxDistance) + { + maxDistance = distance; + targetZone = otherZone; + } + } + if (targetZone) //TODO: consider refactoring duplicated code + { + float3 vec = targetZone->getCenter() - ourCenter; + float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; + 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()); + + misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size + logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); + } + } + else + { + //Move misplaced zone away from overlapping zone + //FIXME: Does that ever happend? Check the number ranges and rescale if needed + + float maxOverlap = 0; + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + + if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) + continue; + + auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); + if (distance > maxOverlap) + { + maxOverlap = distance; + targetZone = otherZone.second; + } + } + if (targetZone) + { + float3 vec = ourCenter - targetZone->getCenter(); + float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; + 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()); + + misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated + logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); + } + } + //Don't swap that zone in next iteration + lastSwappedZones.insert(misplacedZone->getId()); } float CZonePlacer::metric (const int3 &A, const int3 &B) const diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 4750223a0..2dfd3208a 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -45,7 +45,7 @@ private: void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand); void attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const; void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps); - void moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps) const; + void moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps); private: int width; @@ -65,6 +65,7 @@ private: //distance [a][b] = number of zone connections required to travel between the zones std::map> distancesBetweenZones; + std::set lastSwappedZones; RmgMap & map; }; From 55d7d7b9b57b4bc18f6ef30234d9ff536873419d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 20 Apr 2023 12:44:32 +0200 Subject: [PATCH 17/19] Fix coefficients to make dead code actually be used sometimes. --- lib/rmg/CZonePlacer.cpp | 13 ++++--------- lib/rmg/CZonePlacer.h | 9 +++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 466dc2637..eb2340cc8 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -30,6 +30,8 @@ CZonePlacer::CZonePlacer(RmgMap & map) stiffnessConstant(3e-3f), stifness(0), stiffnessIncreaseFactor(1.05f), + bestTotalDistance(1e10), + bestTotalOverlap(1e10), map(map) { } @@ -321,10 +323,6 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) //0. set zone sizes and surface / underground level prepareZones(zones, zonesVector, underground, rand); - //remember best solution - float bestTotalDistance = 1e10; - float bestTotalOverlap = 1e10; - std::map, float3> bestSolution; TForceVector forces; @@ -673,7 +671,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista auto misplacedZone = misplacedZones.front().second; float3 ourCenter = misplacedZone->getCenter(); - if (totalDistance > totalOverlap) + if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) { //Move one zone towards most distant zone to reduce distance @@ -688,7 +686,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista targetZone = otherZone; } } - if (targetZone) //TODO: consider refactoring duplicated code + if (targetZone) { float3 vec = targetZone->getCenter() - ourCenter; float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; @@ -696,13 +694,11 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista logGlobal->trace("direction is %s", vec.toString()); misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size - logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); } } else { //Move misplaced zone away from overlapping zone - //FIXME: Does that ever happend? Check the number ranges and rescale if needed float maxOverlap = 0; for(const auto & otherZone : zones) @@ -727,7 +723,6 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista logGlobal->trace("direction is %s", vec.toString()); misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated - logGlobal->trace("New distance %f", targetZone->getCenter().dist2d(misplacedZone->getCenter())); } } //Don't swap that zone in next iteration diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 2dfd3208a..34e24f6f9 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -50,7 +50,7 @@ private: private: int width; int height; - //metric coefiicients + //metric coeficients float scaleX; float scaleY; float mapSize; @@ -59,9 +59,10 @@ private: float stiffnessConstant; float stifness; float stiffnessIncreaseFactor; - //float a1, b1, c1, a2, b2, c2; - //CMap * map; - //std::unique_ptr graph; + + //remember best solution + float bestTotalDistance; + float bestTotalOverlap; //distance [a][b] = number of zone connections required to travel between the zones std::map> distancesBetweenZones; From 09b493681b5d4775270de10055d941e069a80d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 20 Apr 2023 16:13:30 +0200 Subject: [PATCH 18/19] More iterations, higher gravity constant for better results. --- lib/rmg/CZonePlacer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index eb2340cc8..61b655cd0 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -26,10 +26,10 @@ class CRandomGenerator; CZonePlacer::CZonePlacer(RmgMap & map) : width(0), height(0), scaleX(0), scaleY(0), mapSize(0), - gravityConstant(5e-4f), + gravityConstant(1e-3f), stiffnessConstant(3e-3f), stifness(0), - stiffnessIncreaseFactor(1.05f), + stiffnessIncreaseFactor(1.03f), bestTotalDistance(1e10), bestTotalOverlap(1e10), map(map) From 88c436691dd22b95eda715283f7cb629700a669e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 20 Apr 2023 16:26:06 +0200 Subject: [PATCH 19/19] Simplify logs --- lib/rmg/CZonePlacer.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 61b655cd0..3b60ca650 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -690,8 +690,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista { float3 vec = targetZone->getCenter() - ourCenter; float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; - 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()); + 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()); misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size } @@ -719,8 +718,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista { float3 vec = ourCenter - targetZone->getCenter(); float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; - 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()); + 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()); misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated }