Add new map types (#10695)

* fractal map type

* repeat create fractal until enough land

* small changes and add map types Lakes and Small Continents

* fix for removing land at edges of hexagonal maps

* readability
This commit is contained in:
Rémi Dufour
2023-12-13 21:37:27 +01:00
committed by GitHub
parent d2dde08824
commit 153a90623c
4 changed files with 102 additions and 2 deletions

View File

@ -145,7 +145,10 @@ object MapType : IsPartOfGameInfoSerialization {
const val threeContinents = "Three Continents"
const val fourCorners = "Four Corners"
const val archipelago = "Archipelago"
const val fractal = "Fractal"
const val innerSea = "Inner Sea"
const val lakes = "Lakes"
const val smallContinents = "Small Continents"
// All ocean tiles
const val empty = "Empty"

View File

@ -39,7 +39,6 @@ class MapGenerationRandomness {
return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
}
fun chooseSpreadOutLocations(number: Int, suitableTiles: List<Tile>, mapRadius: Int): ArrayList<Tile> {
if (number <= 0) return ArrayList(0)

View File

@ -51,6 +51,9 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
MapType.fourCorners -> createFourCorners(tileMap)
MapType.archipelago -> createArchipelago(tileMap)
MapType.perlin -> createPerlin(tileMap)
MapType.fractal -> createFractal(tileMap)
MapType.lakes -> createLakes(tileMap)
MapType.smallContinents -> createSmallContinents(tileMap)
}
if (tileMap.mapParameters.shape === MapShape.flatEarth) {
@ -96,6 +99,48 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
}
}
private fun createFractal(tileMap: TileMap) {
do {
val elevationSeed = randomness.RNG.nextInt().toDouble()
for (tile in tileMap.values) {
val maxdim = max(tileMap.maxLatitude, tileMap.maxLongitude)
var ratio = maxdim / 32.0 // change scale depending on map size so that average number of continents stay the same
if (tileMap.mapParameters.shape === MapShape.hexagonal || tileMap.mapParameters.shape === MapShape.flatEarth) {
ratio *= 0.5 // In hexagonal type map for some reason it tends to make a single continent like pangaea if we don't diminish the scale
}
var elevation = randomness.getPerlinNoise(tile, elevationSeed, persistence=0.8, lacunarity=1.5, scale=ratio*30.0)
elevation += getOceanEdgesTransform(tile, tileMap)
spawnLandOrWater(tile, elevation)
}
waterThreshold -= 0.01
} while (tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70% water
}
private fun createLakes(tileMap: TileMap) {
val elevationSeed = randomness.RNG.nextInt().toDouble()
for (tile in tileMap.values) {
val elevation = 0.3 - getRidgedPerlinNoise(tile, elevationSeed, persistence=0.7, lacunarity=1.5)
spawnLandOrWater(tile, elevation)
}
}
private fun createSmallContinents(tileMap: TileMap) {
val elevationSeed = randomness.RNG.nextInt().toDouble()
waterThreshold += 0.25
do {
for (tile in tileMap.values) {
var elevation = getRidgedPerlinNoise(tile, elevationSeed, scale = 22.0)
elevation += getOceanEdgesTransform(tile, tileMap)
spawnLandOrWater(tile, elevation)
}
waterThreshold -= 0.01
} while (tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70%
}
private fun createArchipelago(tileMap: TileMap) {
val elevationSeed = randomness.RNG.nextInt().toDouble()
waterThreshold += 0.25
@ -115,9 +160,9 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
}
tileMap.assignContinents(TileMap.AssignContinentsMode.Reassign)
waterThreshold -= 0.01
} while (tileMap.continentSizes.values.count { it > 25 } != 1 // Multiple large continents
|| tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70% water
tileMap.assignContinents(TileMap.AssignContinentsMode.Clear)
}
@ -312,6 +357,56 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
return 1.0 - (5.0 * shouldBeWater*shouldBeWater + randomScale) / 3.0
}
private fun getOceanEdgesTransform(tile: Tile, tileMap: TileMap): Double {
// The idea is to reduce elevation at the border of the map, so that we have mostly ocean there.
val maxX = tileMap.maxLongitude
val maxY = tileMap.maxLatitude
val x = tile.longitude
val y = tile.latitude
var elevationOffset = 0.0
val xdistanceratio = abs(x) / maxX
val ydistanceratio = abs(y) / maxY
if (tileMap.mapParameters.shape === MapShape.hexagonal || tileMap.mapParameters.shape === MapShape.flatEarth) {
val startdropoffratio = 0.8 // distance from center at which we start decreasing elevation linearly
val xdrsquared = xdistanceratio * xdistanceratio
val ydrsquared = ydistanceratio * ydistanceratio
val distancefromcenter = sqrt(xdrsquared+ydrsquared)
var distanceoffset = 0.0
if (distancefromcenter > startdropoffratio) {
val dropoffdistance = distancefromcenter - startdropoffratio
val normalizationDivisor = 1.0 - startdropoffratio // for normalizing to [0;1] range
distanceoffset = dropoffdistance / normalizationDivisor
}
elevationOffset -= distanceoffset * 0.35
} else {
var xoffset = 0.0
val xstartdropoffratio = 0.8
if (xdistanceratio > xstartdropoffratio) {
val xdropoffdistance = xdistanceratio - xstartdropoffratio
val xnormalizationdivisor = 1.0 - xstartdropoffratio // for normalizing to [0;1] range
xoffset = xdropoffdistance / xnormalizationdivisor
}
var yoffset = 0.0
val ystartdropoffratio = 0.76 // we want to have enough space at the north and south for circumnavigation
if (ydistanceratio > ystartdropoffratio) {
val ydropoffdistance = ydistanceratio - ystartdropoffratio
val ynormalizationdivisor = 1.0 - ystartdropoffratio // for normalizing to [0;1] range
yoffset = ydropoffdistance / ynormalizationdivisor
}
// these factors were just found by trial and error to be adequate for having enough space while not reducing land too much.
elevationOffset -= xoffset * 0.33
elevationOffset -= yoffset * 0.35
}
return max(elevationOffset, -0.35)
}
/**
* Generates ridged perlin noise. As for parameters see [MapGenerationRandomness.getPerlinNoise]
*/

View File

@ -139,6 +139,9 @@ class MapParametersTable(
MapType.archipelago,
MapType.innerSea,
MapType.perlin,
MapType.fractal,
MapType.lakes,
MapType.smallContinents,
if (forMapEditor && mapGeneratedMainType != MapGeneratedMainType.randomGenerated) MapType.empty else null
)