Browse Source

Merge pull request #4954 from vcmi/curved_roads

Curved roads
DjWarmonger 1 year ago
parent
commit
9e07b5c5de

+ 17 - 0
lib/rmg/Functions.cpp

@@ -24,6 +24,23 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+void replaceWithCurvedPath(rmg::Path & path, const Zone & zone, const int3 & src, bool onlyStraight)
+{
+	auto costFunction = rmg::Path::createCurvedCostFunction(zone.area()->getBorder());
+	auto pathArea = zone.areaForRoads();
+	rmg::Path curvedPath(pathArea);
+	curvedPath.connect(zone.freePaths().get());
+	curvedPath = curvedPath.search(src, onlyStraight, costFunction);
+	if (curvedPath.valid())
+	{
+		path = curvedPath;
+	}
+	else
+	{
+		logGlobal->warn("Failed to create curved path to %s", src.toString());
+	}
+}
+
 rmg::Tileset collectDistantTiles(const Zone& zone, int distance)
 {
 	uint32_t distanceSq = distance * distance;

+ 2 - 0
lib/rmg/Functions.h

@@ -34,6 +34,8 @@ public:
 	}
 };
 
+void replaceWithCurvedPath(rmg::Path & path, const Zone & zone, const int3 & src, bool onlyStraight = true);
+
 rmg::Tileset collectDistantTiles(const Zone & zone, int distance);
 
 int chooseRandomAppearance(vstd::RNG & generator, si32 ObjID, TerrainId terrain);

+ 18 - 2
lib/rmg/RmgPath.cpp

@@ -116,8 +116,7 @@ Path Path::search(const Tileset & dst, bool straight, std::function<float(const
 				if(!result.dArea->contains(pos))
 					return;
 				
-				float movementCost = moveCostFunction(currentNode, pos) + currentNode.dist2d(pos);
-				
+				float movementCost = moveCostFunction(currentNode, pos);
 				float distance = distances[currentNode] + movementCost; //we prefer to use already free paths
 				int bestDistanceSoFar = std::numeric_limits<int>::max();
 				auto it = distances.find(pos);
@@ -190,4 +189,21 @@ const Area & Path::getPathArea() const
 	return dPath;
 }
 
+Path::MoveCostFunction Path::createCurvedCostFunction(const Area & border)
+{
+	// Capture by value to ensure the Area object persists
+	return [border = border](const int3& src, const int3& dst) -> float
+	{
+		// Route main roads far from border
+		float ret = dst.dist2d(src);
+		float dist = border.distanceSqr(dst);
+
+		if(dist > 1.0f)
+		{
+			ret /= dist;
+		}
+		return ret;
+	};
+}
+
 VCMI_LIB_NAMESPACE_END

+ 3 - 1
lib/rmg/RmgPath.h

@@ -21,7 +21,8 @@ namespace rmg
 class Path
 {
 public:
-	const static std::function<float(const int3 &, const int3 &)> DEFAULT_MOVEMENT_FUNCTION;
+	using MoveCostFunction = std::function<float(const int3 &, const int3 &)>;
+	const static MoveCostFunction DEFAULT_MOVEMENT_FUNCTION;
 	
 	Path(const Area & area);
 	Path(const Area & area, const int3 & src);
@@ -42,6 +43,7 @@ public:
 	const Area & getPathArea() const;
 	
 	static Path invalid();
+	static MoveCostFunction createCurvedCostFunction(const Area & border);
 	
 private:
 	

+ 5 - 1
lib/rmg/Zone.cpp

@@ -121,6 +121,11 @@ ThreadSafeProxy<const rmg::Area> Zone::areaUsed() const
 	return ThreadSafeProxy<const rmg::Area>(dAreaUsed, areaMutex);
 }
 
+rmg::Area Zone::areaForRoads() const
+{
+	return areaPossible() + freePaths();
+}
+
 void Zone::clearTiles()
 {
 	Lock lock(areaMutex);
@@ -299,7 +304,6 @@ void Zone::fractalize()
 	logGlobal->trace("Zone %d: treasureValue %d blockDistance: %2.f, freeDistance: %2.f", getId(), treasureValue, blockDistance, freeDistance);
 
 	Lock lock(areaMutex);
-	// FIXME: Do not access Area directly
 
 	rmg::Area clearedTiles(dAreaFree);
 	rmg::Area possibleTiles(dAreaPossible);

+ 2 - 0
lib/rmg/Zone.h

@@ -92,6 +92,8 @@ public:
 	ThreadSafeProxy<const rmg::Area> freePaths() const;
 	ThreadSafeProxy<rmg::Area> areaUsed();
 	ThreadSafeProxy<const rmg::Area> areaUsed() const;
+	
+	rmg::Area areaForRoads() const;
 
 	void initFreeTiles();
 	void clearTiles();

+ 17 - 16
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -185,8 +185,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 							return 1.f / (1.f + border.distanceSqr(d));
 						};
 
-						auto ourArea = zone.areaPossible() + zone.freePaths();
-						auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
+						auto ourArea = zone.areaForRoads();
+						auto theirArea = otherZone->areaForRoads();
 						theirArea.add(potentialPos);
 						rmg::Path ourPath(ourArea);
 						rmg::Path theirPath(theirArea);
@@ -278,24 +278,22 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 			assert(zone.getModificator<ObjectManager>());
 			auto & manager = *zone.getModificator<ObjectManager>();
 			auto * monsterType = manager.chooseGuard(connection.getGuardStrength(), true);
-			
+		
 			rmg::Area border(zone.area()->getBorder());
 			border.unite(otherZone->area()->getBorder());
-			
-			auto costFunction = [&border](const int3 & s, const int3 & d)
-			{
-				return 1.f / (1.f + border.distanceSqr(d));
-			};
-			
-			auto ourArea = zone.areaPossible() + zone.freePaths();
-			auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
+
+			auto localCostFunction = rmg::Path::createCurvedCostFunction(zone.area()->getBorder());
+			auto otherCostFunction = rmg::Path::createCurvedCostFunction(otherZone->area()->getBorder());
+
+			auto ourArea = zone.areaForRoads();
+			auto theirArea = otherZone->areaForRoads();
 			theirArea.add(guardPos);
 			rmg::Path ourPath(ourArea);
 			rmg::Path theirPath(theirArea);
 			ourPath.connect(zone.freePaths().get());
-			ourPath = ourPath.search(guardPos, true, costFunction);
+			ourPath = ourPath.search(guardPos, true, localCostFunction);
 			theirPath.connect(otherZone->freePaths().get());
-			theirPath = theirPath.search(guardPos, true, costFunction);
+			theirPath = theirPath.search(guardPos, true, otherCostFunction);
 			
 			if(ourPath.valid() && theirPath.valid())
 			{
@@ -417,11 +415,14 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 			
 			if(path1.valid() && path2.valid())
 			{
-				zone.connectPath(path1);
-				otherZone->connectPath(path2);
-				
 				manager.placeObject(rmgGate1, guarded1, true, allowRoad);
 				managerOther.placeObject(rmgGate2, guarded2, true, allowRoad);
+
+				replaceWithCurvedPath(path1, zone, rmgGate1.getVisitablePosition());
+				replaceWithCurvedPath(path2, *otherZone, rmgGate2.getVisitablePosition());
+
+				zone.connectPath(path1);
+				otherZone->connectPath(path2);
 				
 				assert(otherZone->getModificator<ConnectionsPlacer>());
 				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);

+ 9 - 1
lib/rmg/modificators/ObjectManager.cpp

@@ -344,7 +344,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 {
 	int3 pos;
 	auto possibleArea = searchArea;
-	auto cachedArea = zone.areaPossible() + zone.freePaths();
+	auto cachedArea = zone.areaForRoads();
 	while(true)
 	{
 		pos = findPlaceForObject(possibleArea, obj, weightFunction, optimizer);
@@ -419,6 +419,9 @@ bool ObjectManager::createMonoliths()
 			return false;
 		}
 		
+		// Once it can be created, replace with curved path
+		replaceWithCurvedPath(path, zone, rmgObject.getVisitablePosition());
+		
 		zone.connectPath(path);
 		placeObject(rmgObject, guarded, true, objInfo.createRoad);
 	}
@@ -449,6 +452,11 @@ bool ObjectManager::createRequiredObjects()
 			logGlobal->error("Failed to fill zone %d due to lack of space", zone.getId());
 			return false;
 		}
+		if (objInfo.createRoad)
+		{
+			// Once valid path can be created, replace with curved path
+			replaceWithCurvedPath(path, zone, rmgObject.getVisitablePosition());
+		}
 		
 		zone.connectPath(path);
 		placeObject(rmgObject, guarded, true, objInfo.createRoad);

+ 1 - 1
lib/rmg/modificators/RiverPlacer.cpp

@@ -212,7 +212,7 @@ void RiverPlacer::preprocess()
 	{
 		auto river = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river;
 		auto & a = neighbourZonesTiles[connectedToWaterZoneId];
-		auto availableArea = zone.areaPossible() + zone.freePaths();
+		auto availableArea = zone.areaForRoads();
 		for(const auto & tileToProcess : availableArea.getTilesVector())
 		{
 			int templateId = -1;

+ 4 - 4
lib/rmg/modificators/WaterProxy.cpp

@@ -266,9 +266,9 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, bool createRoad, Rout
 	rmg::Object rmgObject(*boat);
 	rmgObject.setTemplate(zone.getTerrainType(), zone.getRand());
 
-	auto waterAvailable = zone.areaPossible() + zone.freePaths();
+	auto waterAvailable = zone.areaForRoads();
 	rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
-	coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
+	coast.intersect(land.areaForRoads()); //having only available land tiles
 	auto boardingPositions = coast.getSubarea([&waterAvailable, this](const int3 & tile) //tiles where boarding is possible
 		{
 			//We don't want place boat right to any land object, especiallly the zone guard
@@ -332,10 +332,10 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool
 	rmgObject.setTemplate(land.getTerrainType(), zone.getRand());
 	bool guarded = manager->addGuard(rmgObject, guard);
 	
-	auto waterAvailable = zone.areaPossible() + zone.freePaths();
+	auto waterAvailable = zone.areaForRoads();
 	waterAvailable.intersect(lake.area);
 	rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
-	coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
+	coast.intersect(land.areaForRoads()); //having only available land tiles
 	auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible
 	{
 		rmg::Area a({tile});