mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 23:39:40 +07:00
Map Generation Fixes and Tweaks (#4069)
* * Renamed Continents to Two Continents * Created Four Continents map size It spawns a vertical and a horizontal ocean to split the map in 4 major continents. Works properly on all sizes and with world wrap * Fixed Pangea extending to boundaries in rectangular maps * Unified Pangea for rectangular and hexagonal maps * Fixed Default map type bias on Rectangular maps. Also removed "pangea effect" on Default: land can spawn with the same probability in the whole map. * New map type: 4 Corners * Reverted name change Two Continents to Continents * Renamed Four Continents to Four Corners (to reflect original name in Civ5) * Added translation strings * New map type: 4 Corners * Reverted name change Two Continents to Continents * Renamed Four Continents to Four Corners (to reflect original name in Civ5) * Added translation strings
This commit is contained in:
@ -212,6 +212,7 @@ Default =
|
||||
Pangaea =
|
||||
Perlin =
|
||||
Continents =
|
||||
Four Corners =
|
||||
Archipelago =
|
||||
Number of City-States =
|
||||
One City Challenge =
|
||||
|
@ -102,6 +102,7 @@ object MapShape {
|
||||
object MapType {
|
||||
const val pangaea = "Pangaea"
|
||||
const val continents = "Continents"
|
||||
const val fourCorners = "Four Corners"
|
||||
const val perlin = "Perlin"
|
||||
const val archipelago = "Archipelago"
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.map.*
|
||||
@ -23,6 +22,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
when (tileMap.mapParameters.type) {
|
||||
MapType.pangaea -> createPangea(tileMap)
|
||||
MapType.continents -> createTwoContinents(tileMap)
|
||||
MapType.fourCorners -> createFourCorners(tileMap)
|
||||
MapType.perlin -> createPerlin(tileMap)
|
||||
MapType.archipelago -> createArchipelago(tileMap)
|
||||
MapType.default -> generateLandCellularAutomata(tileMap)
|
||||
@ -36,12 +36,17 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun smooth(tileMap: TileMap, randomness: MapGenerationRandomness) {
|
||||
/**
|
||||
* Smoothen the map by clustering landmass and oceans with probability [threshold].
|
||||
* [TileInfo]s with more than 3 land neighbours are converted to land.
|
||||
* [TileInfo]s with more than 3 water neighbours are converted to water.
|
||||
*/
|
||||
private fun smoothen(tileMap: TileMap, threshold: Double = 1.0) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
||||
if (randomness.RNG.nextFloat() < 0.5f)
|
||||
if (randomness.RNG.nextFloat() > threshold)
|
||||
continue
|
||||
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = Constants.grassland
|
||||
else if (numberOfLandNeighbors < 3)
|
||||
@ -69,7 +74,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getCircularNoise(tile, tileMap)) / 2.0
|
||||
elevation = (elevation + getEllipticContinent(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
@ -83,9 +88,29 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCircularNoise(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
private fun createFourCorners(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getFourCornersTransform(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an elevation map that favors a central elliptic continent spanning over 85% - 95% of
|
||||
* the map size.
|
||||
*/
|
||||
private fun getEllipticContinent(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val randomScale = randomness.RNG.nextDouble()
|
||||
val distanceFactor = percentualDistanceToCenter(tileInfo, tileMap)
|
||||
val ratio = 0.85 + 0.1 * randomness.RNG.nextDouble()
|
||||
|
||||
val a = ratio * tileMap.maxLongitude
|
||||
val b = ratio * tileMap.maxLatitude
|
||||
val x = tileInfo.longitude
|
||||
val y = tileInfo.latitude
|
||||
|
||||
val distanceFactor = x * x / (a * a) + y * y / (b * b)
|
||||
|
||||
return min(0.3, 1.0 - (5.0 * distanceFactor * distanceFactor + randomScale) / 3.0)
|
||||
}
|
||||
@ -107,19 +132,35 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
return min(0.2, -1.0 + (5.0 * longitudeFactor.pow(0.6f) + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
private fun percentualDistanceToCenter(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val mapRadius = tileMap.mapParameters.mapSize.radius
|
||||
if (tileMap.mapParameters.shape == MapShape.hexagonal)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / mapRadius
|
||||
else {
|
||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / HexMath.getDistance(Vector2.Zero, Vector2(size.x / 2, size.y / 2))
|
||||
}
|
||||
}
|
||||
private fun getFourCornersTransform(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
// The idea here is to create a water area separating the two four areas.
|
||||
// So what we do it create a line of water in the middle - where longitude is close to 0.
|
||||
val randomScale = randomness.RNG.nextDouble()
|
||||
var longitudeFactor = abs(tileInfo.longitude) / tileMap.maxLongitude
|
||||
var latitudeFactor = abs(tileInfo.latitude) / tileMap.maxLatitude
|
||||
|
||||
// If this is a world wrap, we want it to be separated on both sides -
|
||||
// so we make the actual strip of water thinner, but we put it both in the middle of the map and on the edges of the map
|
||||
if (tileMap.mapParameters.worldWrap) {
|
||||
longitudeFactor = min(
|
||||
longitudeFactor,
|
||||
(tileMap.maxLongitude - abs(tileInfo.longitude)) / tileMap.maxLongitude
|
||||
) * 1.5f
|
||||
latitudeFactor = min(
|
||||
latitudeFactor,
|
||||
(tileMap.maxLatitude - abs(tileInfo.latitude)) / tileMap.maxLatitude
|
||||
) * 1.5f
|
||||
}
|
||||
// there's nothing magical about this, it's just what we got from playing around with a lot of different options -
|
||||
// the numbers can be changed if you find that something else creates better looking continents
|
||||
|
||||
val landFactor = min(longitudeFactor, latitudeFactor)
|
||||
|
||||
return min(0.2, -1.0 + (5.0 * landFactor.pow(0.5f) + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ridged perlin noise. As for parameters see [getPerlinNoise]
|
||||
* Generates ridged perlin noise. As for parameters see [MapGenerationRandomness.getPerlinNoise]
|
||||
*/
|
||||
private fun getRidgedPerlinNoise(tile: TileInfo, seed: Double,
|
||||
nOctaves: Int = 10,
|
||||
@ -132,46 +173,15 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
|
||||
// region Cellular automata
|
||||
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
||||
val numSmooth = 4
|
||||
|
||||
// init
|
||||
for (tile in tileMap.values) {
|
||||
val terrainType = getInitialTerrainCellularAutomata(tile, tileMap.mapParameters)
|
||||
if (terrainType == TerrainType.Land) tile.baseTerrain = Constants.grassland
|
||||
else tile.baseTerrain = Constants.ocean
|
||||
tile.baseTerrain =
|
||||
if (randomness.RNG.nextDouble() < 0.55) Constants.grassland else Constants.ocean
|
||||
|
||||
tile.setTransients()
|
||||
}
|
||||
|
||||
//smooth
|
||||
val grassland = Constants.grassland
|
||||
val ocean = Constants.ocean
|
||||
|
||||
for (loop in 0..numSmooth) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == grassland }
|
||||
if (tileInfo.baseTerrain == grassland) { // land tile
|
||||
if (numberOfLandNeighbors < 3)
|
||||
tileInfo.baseTerrain = ocean
|
||||
} else { // water tile
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = grassland
|
||||
smoothen(tileMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapParameters: MapParameters): TerrainType {
|
||||
val mapRadius = mapParameters.mapSize.radius
|
||||
|
||||
// default
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.9f * mapRadius) {
|
||||
if (randomness.RNG.nextDouble() < 0.1) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.85f * mapRadius) {
|
||||
if (randomness.RNG.nextDouble() < 0.2) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (randomness.RNG.nextDouble() < 0.55) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
@ -68,6 +68,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
|
||||
MapType.default,
|
||||
MapType.pangaea,
|
||||
MapType.continents,
|
||||
MapType.fourCorners,
|
||||
MapType.perlin,
|
||||
MapType.archipelago,
|
||||
if (isEmptyMapAllowed) MapType.empty else null
|
||||
|
Reference in New Issue
Block a user