mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 07:18:57 +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 =
|
Pangaea =
|
||||||
Perlin =
|
Perlin =
|
||||||
Continents =
|
Continents =
|
||||||
|
Four Corners =
|
||||||
Archipelago =
|
Archipelago =
|
||||||
Number of City-States =
|
Number of City-States =
|
||||||
One City Challenge =
|
One City Challenge =
|
||||||
|
@ -102,6 +102,7 @@ object MapShape {
|
|||||||
object MapType {
|
object MapType {
|
||||||
const val pangaea = "Pangaea"
|
const val pangaea = "Pangaea"
|
||||||
const val continents = "Continents"
|
const val continents = "Continents"
|
||||||
|
const val fourCorners = "Four Corners"
|
||||||
const val perlin = "Perlin"
|
const val perlin = "Perlin"
|
||||||
const val archipelago = "Archipelago"
|
const val archipelago = "Archipelago"
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.unciv.logic.map.mapgenerator
|
package com.unciv.logic.map.mapgenerator
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.HexMath
|
import com.unciv.logic.HexMath
|
||||||
import com.unciv.logic.map.*
|
import com.unciv.logic.map.*
|
||||||
@ -23,6 +22,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
|||||||
when (tileMap.mapParameters.type) {
|
when (tileMap.mapParameters.type) {
|
||||||
MapType.pangaea -> createPangea(tileMap)
|
MapType.pangaea -> createPangea(tileMap)
|
||||||
MapType.continents -> createTwoContinents(tileMap)
|
MapType.continents -> createTwoContinents(tileMap)
|
||||||
|
MapType.fourCorners -> createFourCorners(tileMap)
|
||||||
MapType.perlin -> createPerlin(tileMap)
|
MapType.perlin -> createPerlin(tileMap)
|
||||||
MapType.archipelago -> createArchipelago(tileMap)
|
MapType.archipelago -> createArchipelago(tileMap)
|
||||||
MapType.default -> generateLandCellularAutomata(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) {
|
for (tileInfo in tileMap.values) {
|
||||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
if (randomness.RNG.nextFloat() > threshold)
|
||||||
if (randomness.RNG.nextFloat() < 0.5f)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
||||||
if (numberOfLandNeighbors > 3)
|
if (numberOfLandNeighbors > 3)
|
||||||
tileInfo.baseTerrain = Constants.grassland
|
tileInfo.baseTerrain = Constants.grassland
|
||||||
else if (numberOfLandNeighbors < 3)
|
else if (numberOfLandNeighbors < 3)
|
||||||
@ -69,7 +74,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
|||||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||||
for (tile in tileMap.values) {
|
for (tile in tileMap.values) {
|
||||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
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())
|
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 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)
|
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)
|
return min(0.2, -1.0 + (5.0 * longitudeFactor.pow(0.6f) + randomScale) / 3.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun percentualDistanceToCenter(tileInfo: TileInfo, tileMap: TileMap): Double {
|
private fun getFourCornersTransform(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||||
val mapRadius = tileMap.mapParameters.mapSize.radius
|
// The idea here is to create a water area separating the two four areas.
|
||||||
if (tileMap.mapParameters.shape == MapShape.hexagonal)
|
// So what we do it create a line of water in the middle - where longitude is close to 0.
|
||||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / mapRadius
|
val randomScale = randomness.RNG.nextDouble()
|
||||||
else {
|
var longitudeFactor = abs(tileInfo.longitude) / tileMap.maxLongitude
|
||||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
var latitudeFactor = abs(tileInfo.latitude) / tileMap.maxLatitude
|
||||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / HexMath.getDistance(Vector2.Zero, Vector2(size.x / 2, size.y / 2))
|
|
||||||
|
// 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,
|
private fun getRidgedPerlinNoise(tile: TileInfo, seed: Double,
|
||||||
nOctaves: Int = 10,
|
nOctaves: Int = 10,
|
||||||
@ -132,46 +173,15 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
|||||||
|
|
||||||
// region Cellular automata
|
// region Cellular automata
|
||||||
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
||||||
val numSmooth = 4
|
|
||||||
|
|
||||||
// init
|
|
||||||
for (tile in tileMap.values) {
|
for (tile in tileMap.values) {
|
||||||
val terrainType = getInitialTerrainCellularAutomata(tile, tileMap.mapParameters)
|
tile.baseTerrain =
|
||||||
if (terrainType == TerrainType.Land) tile.baseTerrain = Constants.grassland
|
if (randomness.RNG.nextDouble() < 0.55) Constants.grassland else Constants.ocean
|
||||||
else tile.baseTerrain = Constants.ocean
|
|
||||||
tile.setTransients()
|
tile.setTransients()
|
||||||
}
|
}
|
||||||
|
|
||||||
//smooth
|
smoothen(tileMap)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// endregion
|
||||||
}
|
}
|
@ -68,6 +68,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
|
|||||||
MapType.default,
|
MapType.default,
|
||||||
MapType.pangaea,
|
MapType.pangaea,
|
||||||
MapType.continents,
|
MapType.continents,
|
||||||
|
MapType.fourCorners,
|
||||||
MapType.perlin,
|
MapType.perlin,
|
||||||
MapType.archipelago,
|
MapType.archipelago,
|
||||||
if (isEmptyMapAllowed) MapType.empty else null
|
if (isEmptyMapAllowed) MapType.empty else null
|
||||||
|
Reference in New Issue
Block a user