TerraForged/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/WorldHeightmap.java
2020-01-16 10:06:28 +00:00

207 lines
7.7 KiB
Java

package com.terraforged.core.world.heightmap;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.func.EdgeFunc;
import me.dags.noise.func.Interpolation;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.module.Blender;
import com.terraforged.core.module.Lerp;
import com.terraforged.core.module.MultiBlender;
import com.terraforged.core.module.Selector;
import com.terraforged.core.settings.GeneratorSettings;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.continent.ContinentBlender;
import com.terraforged.core.world.continent.ContinentMultiBlender;
import com.terraforged.core.world.continent.VoronoiContinentModule;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
import com.terraforged.core.world.terrain.provider.TerrainProvider;
public class WorldHeightmap implements Heightmap {
private static final float DEEP_OCEAN_VALUE = 0.2F;
private static final float OCEAN_VALUE = 0.3F;
private static final float BEACH_VALUE = 0.34F;
private static final float COAST_VALUE = 0.4F;
private static final float INLAND_VALUE = 0.6F;
private final Levels levels;
private final Terrains terrain;
private final Settings settings;
private final Climate climate;
private final Populator root;
private final Populator continent;
private final RiverManager riverManager;
private final TerrainProvider terrainProvider;
public WorldHeightmap(GeneratorContext context) {
context = context.copy();
this.levels = context.levels;
this.terrain = context.terrain;
this.settings = context.settings;
this.climate = new Climate(context, this);
Seed seed = context.seed;
Levels levels = context.levels;
GeneratorSettings genSettings = context.settings.generator;
Seed regionSeed = seed.nextSeed();
Seed regionWarp = seed.nextSeed();
int regionWarpScale = 400;
int regionWarpStrength = 200;
RegionConfig regionConfig = new RegionConfig(
regionSeed.get(),
context.settings.generator.land.regionSize,
Source.simplex(regionWarp.next(), regionWarpScale, 2),
Source.simplex(regionWarp.next(), regionWarpScale, 2),
regionWarpStrength
);
// controls where mountain chains form in the world
Module mountainShapeBase = Source.cellEdge(seed.next(), genSettings.land.mountainScale, EdgeFunc.DISTANCE_2_ADD)
.add(Source.cubic(seed.next(), genSettings.land.mountainScale, 1).scale(-0.05));
// sharpens the transition to create steeper mountains
Module mountainShape = mountainShapeBase
.curve(Interpolation.CURVE3)
.clamp(0, 0.9)
.map(0, 1);
// controls the shape of terrain regions
Module regionShape = Source.cell(regionConfig.seed, regionConfig.scale)
.warp(regionConfig.warpX, regionConfig.warpZ, regionConfig.warpStrength);
// the corresponding edges of terrain regions so we can fade out towards borders
Module regionEdge = Source.cellEdge(regionConfig.seed, regionConfig.scale, EdgeFunc.DISTANCE_2_DIV).invert()
.warp(regionConfig.warpX, regionConfig.warpZ, regionConfig.warpStrength)
.pow(1.5)
.clamp(0, 0.75)
.map(0, 1);
this.terrainProvider = context.terrainFactory.create(context, regionConfig, this);
// the voronoi controlled terrain regions
Populator terrainRegions = new Selector(regionShape, terrainProvider.getPopulators());
// the terrain type at region edges
Populator terrainRegionBorders = new TerrainPopulator(terrainProvider.getLandforms().steppe(seed), context.terrain.steppe);
// transitions between the unique terrain regions and the common border terrain
Populator terrain = new Lerp(
regionEdge,
terrainRegionBorders,
terrainRegions
);
// mountain populator
Populator mountains = register(terrainProvider.getLandforms().mountains(seed), context.terrain.mountains);
// controls what's ocean and what's land
this.continent = createContinent(context);
// blends between normal terrain and mountain chains
Populator land = new Blender(
mountainShape,
terrain,
mountains,
0.1F,
0.9F,
0.6F
);
// uses the continent noise to blend between deep ocean, to ocean, to coast
MultiBlender oceans = new ContinentMultiBlender(
climate,
continent,
register(terrainProvider.getLandforms().deepOcean(seed.next()), context.terrain.deepOcean),
register(Source.constant(levels.water(-7)), context.terrain.ocean),
register(Source.constant(levels.water), context.terrain.coast),
DEEP_OCEAN_VALUE, // below == deep, above == transition to shallow
OCEAN_VALUE, // below == transition to deep, above == transition to coast
COAST_VALUE // below == transition to shallow, above == coast
);
// blends between the ocean/coast terrain and land terrains
root = new ContinentBlender(
continent,
oceans,
land,
OCEAN_VALUE, // below == pure ocean
INLAND_VALUE, // above == pure land
COAST_VALUE, // split point
COAST_VALUE - 0.05F
).mask();
this.riverManager = new RiverManager(this, context);
}
public RiverManager getRiverManager() {
return riverManager;
}
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
// initial type
cell.tag = terrain.steppe;
// apply continent value/edge noise
continent.apply(cell, x, z);
// apply actuall heightmap
root.apply(cell, x, z);
// apply rivers
riverManager.apply(cell, x, z);
// apply climate data
if (cell.value <= levels.water) {
climate.apply(cell, x, z, false);
if (cell.tag == terrain.coast) {
cell.tag = terrain.ocean;
}
} else {
int range = settings.generator.biomeEdgeNoise.strength;
float dx = climate.getOffsetX(x, z, range);
float dz = climate.getOffsetZ(x, z, range);
float px = x + dx;
float pz = z + dz;
tag(cell, px, pz);
climate.apply(cell, px, pz, false);
climate.apply(cell, x, z, true);
}
}
@Override
public void tag(Cell<Terrain> cell, float x, float z) {
continent.apply(cell, x, z);
root.tag(cell, x, z);
}
public Climate getClimate() {
return climate;
}
public Populator getPopulator(Terrain terrain) {
return terrainProvider.getPopulator(terrain);
}
private TerrainPopulator register(Module module, Terrain terrain) {
TerrainPopulator populator = new TerrainPopulator(module, terrain);
terrainProvider.registerMixable(populator);
return populator;
}
private Populator createContinent(GeneratorContext context) {
return new VoronoiContinentModule(context.seed, context.settings.generator);
}
}