Implement Valleys mapgen heightmap

An unoptimized duplicate of the valleys code from Minetest.
Does not implement the temperature and humidity adjustments.
master
Treer 2018-09-07 21:42:07 +10:00
parent 84066f26ca
commit a74766a36e
3 changed files with 120 additions and 18 deletions

View File

@ -94,8 +94,7 @@ public class DefaultVersionFeatures implements VersionFeaturesFactory {
.init(commonLayers)
.initExtend(
LayerIds.MINETEST_OCEAN,
LayerIds.MINETEST_OCEAN,
LayerIds.MINETEST_MOUNTAIN
LayerIds.MINETEST_RIVER
).construct()
);
enabledLayers.put(WorldType.V7,
@ -169,6 +168,10 @@ public class DefaultVersionFeatures implements VersionFeaturesFactory {
WorldType.CARPATHIAN,
(seed, mapgenParams, biomeProfile) -> new amidst.minetest.world.oracle.BiomeDataOracleCarpathian(mapgenParams, biomeProfile, seed)
),
new AbstractMap.SimpleEntry<WorldType, TriFunction<Long, MapgenParams, BiomeProfileSelection, IBiomeDataOracle>>(
WorldType.VALLEYS,
(seed, mapgenParams, biomeProfile) -> new amidst.minetest.world.oracle.BiomeDataOracleValleys(mapgenParams, biomeProfile, seed)
),
new AbstractMap.SimpleEntry<WorldType, TriFunction<Long, MapgenParams, BiomeProfileSelection, IBiomeDataOracle>>(
WorldType.FLAT,
(seed, mapgenParams, biomeProfile) -> new amidst.minetest.world.oracle.BiomeDataOracleFlat(mapgenParams, biomeProfile, seed)

View File

@ -8,8 +8,10 @@ import amidst.mojangapi.world.biome.BiomeColor;
// TODO: be able to import these values from map_meta.txt files
public class MapgenValleysParams extends MapgenParams {
public static final int FLAG_VALLEYS_ALT_CHILL = 0x01;
public static final int FLAG_VALLEYS_HUMID_RIVERS = 0x02;
public static final int FLAG_VALLEYS_ALT_CHILL = 0x01;
public static final int FLAG_VALLEYS_HUMID_RIVERS = 0x02;
public static final int FLAG_VALLEYS_VARY_RIVER_DEPTH = 0x04;
public static final int FLAG_VALLEYS_ALT_DRY = 0x08;
public int spflags = FLAG_VALLEYS_ALT_CHILL | FLAG_VALLEYS_HUMID_RIVERS;

View File

@ -31,7 +31,30 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle {
boolean humid_rivers;
boolean use_altitude_chill;
boolean use_altitude_dry;
boolean vary_driver_depth;
float altitude_chill;
float humidity_adjust;
float river_depth_bed;
float river_size_factor;
/**
* Reusable instance of TerrainNoise, to save unnecessary construction/mem-fragmentation
*/
TerrainNoise tempTerrainNoise = new TerrainNoise();
class TerrainNoise {
int x;
int z;
float terrain_height;
float rivers;
float valley;
float valley_profile;
float slope;
float inter_valley_fill;
boolean isRiver;
};
/**
@ -52,6 +75,7 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle {
}
grad_wl = 1 - params.water_level;
river_size_factor = valleysParams.river_size / 100.0f;
try {
// 2D noise
@ -77,27 +101,99 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle {
humid_rivers = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_HUMID_RIVERS) > 0;
use_altitude_chill = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_ALT_CHILL) > 0;
use_altitude_dry = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_ALT_DRY) > 0;
vary_driver_depth = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_VARY_RIVER_DEPTH) > 0;
}
float terrainLevelAtPoint(int x, int z)
{
TerrainNoise tn;
tempTerrainNoise.x = x;
tempTerrainNoise.z = z;
tempTerrainNoise.terrain_height = Noise.NoisePerlin2D(noise_terrain_height.np, x, z, seed);
tempTerrainNoise.rivers = Noise.NoisePerlin2D(noise_rivers.np, x, z, seed);
tempTerrainNoise.valley = Noise.NoisePerlin2D(noise_valley_depth.np, x, z, seed);
tempTerrainNoise.valley_profile = Noise.NoisePerlin2D(noise_valley_profile.np, x, z, seed);
tempTerrainNoise.slope = Noise.NoisePerlin2D(noise_inter_valley_slope.np, x, z, seed);
tempTerrainNoise.inter_valley_fill = 0.f;
float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed);
float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed);
float inter_valley_slope = NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed);
return adjustedTerrainLevelFromNoise(tempTerrainNoise);
}
// This avoids duplicating the code in terrainLevelFromNoise, adding
// only the final step of terrain generation without a noise map.
float adjustedTerrainLevelFromNoise(TerrainNoise tn)
{
float mount = terrainLevelFromNoise(tn);
int y_start = (int)(mount < 0.f ? (mount - 0.5f) : (mount + 0.5f)); // was "myround(muount);", s32 myround(f32 f) { return (s32)(f < 0.f ? (f - 0.5f) : (f + 0.5f)); }
tn.x = x;
tn.z = z;
tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed);
tn.rivers = &rivers;
tn.valley = &valley;
tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed);
tn.slope = &inter_valley_slope;
tn.inter_valley_fill = 0.f;
for (int y = y_start; y <= y_start + 1000; y++) {
float fill = Noise.NoisePerlin3D(noise_inter_valley_fill.np, tn.x, y, tn.z, seed);
return adjustedTerrainLevelFromNoise(&tn); }
if (fill * tn.slope < y - mount) {
mount = Math.max(y - 1, mount); // was using MYMAX(),, #define MYMAX(a, b) ((a) > (b) ? (a) : (b))
break;
}
}
return mount;
}
// This is in a separate function to save the code inside minetest from having
// to maintain two similar sets of complicated code to determine ground level.
float terrainLevelFromNoise(TerrainNoise tn)
{
// The square function changes the behaviour of this noise:
// very often small, and sometimes very high.
float valley_d = tn.valley * tn.valley; // was using MYSQUARE(), #define MYSQUARE(x) (x) * (x)
// valley_d is here because terrain is generally higher where valleys
// are deep (mountains). base represents the height of the
// rivers, most of the surface is above.
float base = tn.terrain_height + valley_d;
// "river" represents the distance from the river
float river = Math.abs(tn.rivers) - river_size_factor;
// Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
// "valley" represents the height of the terrain, from the rivers.
float tv = Math.max(river / tn.valley_profile, 0.0f);
tn.valley = valley_d * (1.0f - (float)Math.exp(-(tv * tv)));
// Approximate height of the terrain at this point
float mount = base + tn.valley;
tn.slope *= tn.valley;
// Base ground is returned as rivers since it's basically the water table.
tn.rivers = base;
// Rivers are placed where "river" is negative, so where the original noise
// value is close to zero.
if (river < 0.0f) {
tn.isRiver = true;
// Use the the function -sqrt(1-x^2) which models a circle
float tr = river / river_size_factor + 1.0f;
float depth = (float) (river_depth_bed *
Math.sqrt(Math.max(0.0f, 1.0f - (tr * tr))));
// base - depth : height of the bottom of the river
// water_level - 3 : don't make rivers below 3 nodes under the surface.
// We use three because that's as low as the swamp biomes go.
// There is no logical equivalent to this using rangelim.
mount =
Math.min(Math.max(base - depth, (float)(params.water_level - 3)), mount);
// Slope has no influence on rivers
tn.slope = 0.0f;
} else {
tn.isRiver = false;
}
return mount;
}
@Override
public short populateArray(CoordinatesInWorld corner, short[][] result, boolean useQuarterResolution) {
@ -134,7 +230,8 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle {
// Add the ocean bitplane
int surface_y = (int)terrainLevelAtPoint(world_x, world_z);
if (surface_y < valleysParams.water_level) biomeValue |= BITPLANE_OCEAN;
if (isMountains) biomeValue |= BITPLANE_MOUNTAIN;
if (tempTerrainNoise.isRiver) biomeValue |= BITPLANE_RIVER;
//if (isMountains) biomeValue |= BITPLANE_MOUNTAIN;
// add the biome index
// (mask the bitplanes in case the biome returned is -1 (NONE)