Basic terrain shaping added.

World height shader "shades" the primitive island height giving variation to land and sea.

World layer shader "shades" the primitive islands with varying layers dependent on biomes.

Mapper values was changed to perform the more targetted task of generating island locations and masks.

Biome selector now takes a random at call to help with seed consistency (due to threading, this may still be problematic).

Chunk generator implements the new changes.

Tests implement new changes.
This commit is contained in:
Harrison Deng 2020-05-02 00:06:58 -05:00
parent a6cebe703b
commit 486a0f837f
8 changed files with 82 additions and 66 deletions

View File

@ -11,7 +11,6 @@ import ca.recrown.islandsurvivalcraft.Utilities;
public class BiomeSelector {
private boolean initialized = false;
private volatile Random random;
private final HashMap<Biome, Float> lands = new HashMap<>();
private HashMap<Float, ArrayList<Biome>> temperaturesForLand = new HashMap<>();
private final HashMap<Float, ArrayList<Biome>> temperaturePartitionedLandBiomes = new HashMap<>();
@ -20,9 +19,7 @@ public class BiomeSelector {
private final HashMap<Float, ArrayList<Biome>> temperaturePartitionedOceanBiomes = new HashMap<>();
public BiomeSelector(Random random) {
this.random = random;
public BiomeSelector() {
temperaturePartitionedLandBiomes.put(0.05f, new ArrayList<>());
temperaturePartitionedLandBiomes.put(0.30f, new ArrayList<>());
temperaturePartitionedLandBiomes.put(0.95f, new ArrayList<>());
@ -32,10 +29,6 @@ public class BiomeSelector {
temperaturePartitionedOceanBiomes.put(0.5f, new ArrayList<>());
}
public BiomeSelector() {
this(new Random());
}
public void initialize() {
if (initialized) throw new IllegalStateException("Biome selector is already initialized.");
@ -197,7 +190,7 @@ public class BiomeSelector {
* @param temperature Minecraft temperature to select biome from.
* @return The randomly selected biome.
*/
public Biome getLandBiome(float temperature) {
public Biome getLandBiome(float temperature, Random random) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
ArrayList<Biome> biomes = null;
if (temperature <= 0.05f) {
@ -218,7 +211,7 @@ public class BiomeSelector {
* @param temperature Minecraft temperature to select biome from.
* @return The randomly selected biome.
*/
public Biome getOceanBiome(float temperature) {
public Biome getOceanBiome(float temperature, Random random) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
ArrayList<Biome> biomes = null;
if (temperature <= 0.00f) {
@ -230,7 +223,4 @@ public class BiomeSelector {
return biomes.get((int) random.nextFloat() * biomes.size());
}
public void setBiome(Random random) {
this.random = random;
}
}

View File

@ -13,14 +13,12 @@ public class IslandWorldMapper implements CoordinateValidatable {
private final Cache<Point2, Double> blockValueCache;
private final SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 8;
private final int noiseOctaves = 4;
private final float islandBlockGenerationPercent = 15f;
private final float exaggerationFactor = 1f;
private final float islandExaggeration = 1f;
private final double noiseFrequency = 1.7D;
private final double noiseAmplitude = 0.5D;
private final double scale = 0.004D;
private final float shoreFactor = 0.05f;
private final float shoreFactor = 0.065f;
private final float shallowPortion = 0.01f;
private final DepthFirstSearch dfs;
@ -117,20 +115,14 @@ public class IslandWorldMapper implements CoordinateValidatable {
Double res = blockValueCache.get(p);
if (res == null) {
double shift = 2 * (islandBlockGenerationPercent / 100f);
double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true);
double noise = 0;
if (rawNoise < 0) {
noise = ( - Math.pow(- rawNoise, exaggerationFactor) - shift);
} else {
noise = Math.pow(rawNoise, islandExaggeration) - shift;
}
double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true) - shift;
double maxNeg = -1 - shift;
double maxPos = 1 - shift;
if (noise < 0) {
res = - noise / maxNeg;
if (rawNoise < 0) {
res = - rawNoise / maxNeg;
} else {
res = noise / maxPos;
res = rawNoise / maxPos;
}
blockValueCache.set(p, res);
return res;

View File

@ -1,5 +1,7 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.block.Biome;
@ -26,6 +28,7 @@ public interface BiomeGenerator {
* @param tempGenerator The temperature generator to be used.
* @param biomeCache Cache for biomes.
* @param chunkGenCache Cache for whether or not the chunk is generated.
* @param random A random that bukkit passes.
*/
public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache<Point2, Biome[]> biomeCache, Cache<Point2, Boolean> chunkGenCache);
public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache<Point2, Biome[]> biomeCache, Cache<Point2, Boolean> chunkGenCache, Random random);
}

View File

@ -46,10 +46,9 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
int seaLevel = world.getSeaLevel();
IslandWorldMapper mapper = new IslandWorldMapper(seed, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(seed);
WorldHeightShader heightShader = new WorldHeightShader(mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
WorldHeightShader heightShader = new WorldHeightShader(seed, mapper, seaLevel, world.getMaxHeight(), 3);
WorldLayerShader layerShader = new WorldLayerShader(seed, seaLevel, maxHeight, mapper);
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
biomeSelector.setBiome(random);
LinkedList<Future<Boolean>> tasks = new LinkedList<>();
ChunkData chunkData = createChunkData(world);
@ -70,7 +69,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
final int localZ = z;
if (biomes[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(biomes, world, chunkX, chunkZ, localX, localZ, mapper, biomeSelector,
temperatureMapGenerator, biomeCache, chunkExistenceCache);
temperatureMapGenerator, biomeCache, chunkExistenceCache, random);
}
if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null.");
tasks.add(exBeta.submit(() -> {
@ -84,7 +83,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
final int worldZ = Utilities.CHUNK_SIZE * chunkZ + localZ;
Biome currentBiome = biomes[localX][localZ];
int terrainHeight = heightShader.getTerrainHeight(worldX, worldZ, currentBiome);
int currentTerrainHeight = terrainHeight - 1;
int currentTerrainHeight = terrainHeight;
int bedrockHeight = random.nextInt(5) + 1;
if (layerShader.hasSpecialLayers(currentBiome)) {
Material currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, currentBiome);
@ -93,14 +92,17 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
currentTerrainHeight --;
}
} else {
chunkData.setBlock(localX, currentTerrainHeight, localZ, layerShader.getSurfaceMaterial(currentBiome));
int transitionHeight = layerShader.getTransitionMaterialThickness(worldX, worldZ, currentBiome);
currentTerrainHeight -= transitionHeight;
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + transitionHeight, localZ + 1, layerShader.getTransitionMaterial(currentBiome));
int surfaceThickness = layerShader.getSurfaceThickness(worldX, worldZ, currentBiome);
currentTerrainHeight -= surfaceThickness;
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + surfaceThickness, localZ + 1, layerShader.getSurfaceMaterial(currentBiome));
int transitionThickness = layerShader.getTransitionMaterialThickness(worldX, worldZ, currentBiome);
currentTerrainHeight -= transitionThickness;
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + transitionThickness, localZ + 1, layerShader.getTransitionMaterial(currentBiome));
}
chunkData.setRegion(localX, bedrockHeight, localZ, localX + 1, currentTerrainHeight + 1, localZ + 1, Material.STONE);
if (terrainHeight < seaLevel) {
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER);
chunkData.setRegion(localX, terrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER);
}
chunkData.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK);
}

View File

@ -1,5 +1,7 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.block.Biome;
@ -15,7 +17,7 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class UniBiomeIslandGenerator implements BiomeGenerator {
@Override
public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen, Cache<Point2, Biome[]> biomeCache, Cache<Point2, Boolean> chunkGenCache) {
public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen, Cache<Point2, Biome[]> biomeCache, Cache<Point2, Boolean> chunkGenCache, Random random) {
int worldX = 16 * chunkX + localX;
int worldZ = 16 * chunkZ + localZ;
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(worldX, worldZ));
@ -31,7 +33,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
//Fine, check if it's ocean.
if (!mapper.isIsland(worldX, worldZ)) {
biomeSet = new Biome[4];
biomeSet[0] = biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ));
biomeSet[0] = biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ), random);
setCacheBiome(worldX, worldZ, biomeSet, biomes, biomeCache);
return;
}
@ -44,9 +46,9 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
search.setStartPosition(worldX, worldZ);
if (!search.findTarget(islandInfo)) {
float temp = tempGen.getTemperature(worldX, worldZ);
if (islandInfo.main == null) islandInfo.main = biomeSelector.getLandBiome(temp);
if (islandInfo.main == null) islandInfo.main = biomeSelector.getLandBiome(temp, random);
if (islandInfo.shore == null) islandInfo.shore = biomeSelector.getShoreBiome(islandInfo.main, temp);
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp);
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp, random);
}
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper, biomeCache);

View File

@ -1,41 +1,49 @@
package ca.recrown.islandsurvivalcraft.world.shaders;
import java.util.Random;
import org.bukkit.block.Biome;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class WorldHeightShader {
private final IslandWorldMapper islandLocator;
private final IslandWorldMapper mapper;
private final SimplexOctaveGenerator shader;
private final int seaLevel;
private final int worldHeight;
private final int minimumHeight;
public WorldHeightShader(IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) {
this.islandLocator = islandLocator;
public WorldHeightShader(long seed, IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) {
this.mapper = islandLocator;
this.seaLevel = seaLevel;
this.worldHeight = worldHeight;
this.minimumHeight = minimumHeight;
this.shader = new SimplexOctaveGenerator(new Random(seed - 1), 8);
this.shader.setScale(0.05d);
}
public int getTerrainHeight(int worldX, int worldZ, Biome biome) {
int height = 0;
String biomeName = biome.name().toLowerCase();
if (biomeName.contains("hills")) {
height = calculateTerrainHeight(worldX, worldZ, 15);
height = calculateTerrainHeight(worldX, worldZ, 30d);
} else if (biomeName.contains("mountains")) {
height = calculateTerrainHeight(worldX, worldZ, 120);
height = calculateTerrainHeight(worldX, worldZ, 70d);
} else if (biomeName.contains("plateau")) {
height = Math.min(calculateTerrainHeight(worldX, worldZ, 50), 30);
height = (int) Math.min(calculateTerrainHeight(worldX, worldZ, 50d), seaLevel + 30d);
} else if (biomeName.contains("modified")) {
height = calculateTerrainHeight(worldX, worldZ, 45);
height = calculateTerrainHeight(worldX, worldZ, 40d);
} else if (biomeName.contains("shattered")) {
height = calculateTerrainHeight(worldX, worldZ, 60);
height = calculateTerrainHeight(worldX, worldZ, 40d);
} else if (biomeName.contains("tall")) {
height = calculateTerrainHeight(worldX, worldZ, 45);
height = calculateTerrainHeight(worldX, worldZ, 30d);
} else if (biomeName.contains("stone")) {
height = calculateTerrainHeight(worldX, worldZ, 70d);
} else if (!biomeName.contains("ocean")) {
height = calculateTerrainHeight(worldX, worldZ, 15);
height = calculateTerrainHeight(worldX, worldZ, 10d);
} else {
height = calculateTerrainHeight(worldX, worldZ, seaLevel);
height = calculateTerrainHeight(worldX, worldZ, seaLevel, 1d, 0.25d);
}
if (height > worldHeight) throw new IllegalStateException("Resulting height is greater than world height! Biome this occurred on: " + biomeName);
@ -43,14 +51,15 @@ public class WorldHeightShader {
return height;
}
private int calculateTerrainHeight(int worldX, int worldZ, int multiplier) {
int blockHeight = 0;
double islandValue = islandLocator.getWorldValue(worldX, worldZ);
if (!islandLocator.isLand(worldX, worldZ)) {
blockHeight = (int) ((1d + islandValue) * (double) multiplier);
} else {
blockHeight += seaLevel + islandValue * multiplier;
private int calculateTerrainHeight(int worldX, int worldZ, double heightFactor, double shift, double exaggerationFactor) {
int res = seaLevel;
double shapeValue = (shader.noise(worldX, worldZ, 1.5d, 0.05d, true) + shift) / (shift + 1d);
if (shift == 1d && exaggerationFactor != 1d) shapeValue = Math.pow(shapeValue, exaggerationFactor);
res += mapper.getWorldValue(worldX, worldZ) * (shapeValue * heightFactor);
return res;
}
return blockHeight;
private int calculateTerrainHeight(int worldX, int worldZ, double heightFactor) {
return calculateTerrainHeight(worldX, worldZ, heightFactor, 0.75d, 1d);
}
}

View File

@ -38,7 +38,7 @@ public class WorldLayerShader {
/**
* Figures out the type of material to be placed at a given world coordinate set.
*
* Returns null when special layering is done and should return to normal generation.
* @param worldX The world x coordinate.
* @param worldZ The world z coordinate.
* @param y The elevation.
@ -74,7 +74,7 @@ public class WorldLayerShader {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert") || biomeName.contains("warm_ocean")) {
return Material.SAND;
} else if (biomeName.contains("deep_ocean")) {
} else if (biomeName.contains("ocean")) {
return Material.GRAVEL;
} else if (biomeName.contains("stone")) {
return Material.STONE;
@ -83,6 +83,16 @@ public class WorldLayerShader {
return Material.GRASS_BLOCK;
}
public int getSurfaceThickness(int worldX, int worldZ, Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach")) {
return (int) (Math.abs(mapper.getWorldValue(worldX, worldZ)) * 5) + 4;
}
return 1;
}
public Material getTransitionMaterial(Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert")) {

View File

@ -3,6 +3,7 @@ package ca.recrown.islandsurvivalcraft.world.generation;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -11,6 +12,7 @@ import java.util.concurrent.TimeUnit;
import org.bukkit.block.Biome;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@ -30,6 +32,9 @@ public class UniBiomeIslandGeneratorTest {
private volatile Cache<Point2, Double> blockValueCache;
private volatile Cache<Point2, Biome[]> biomeCache;
private volatile Cache<Point2, Boolean> chunkExistenceCache;
private final BiomeSelector biomeSelector = new BiomeSelector();
private final Random random = new Random(SEED);
private class BiomeGenTask implements Runnable {
private final int amount;
@ -51,8 +56,6 @@ public class UniBiomeIslandGeneratorTest {
public void generateBiome(int chunkX, int chunkZ) {
IslandWorldMapper mapper = new IslandWorldMapper(SEED, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED);
BiomeSelector biomeSelector = new BiomeSelector();
biomeSelector.initialize();
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE];
@ -60,7 +63,7 @@ public class UniBiomeIslandGeneratorTest {
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
if (biomes[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(biomes, dummyWorld, chunkX, chunkZ, localX, localZ, mapper,
biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache);
biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache, random);
}
if (biomes[localX][localZ] == null)
throw new IllegalStateException("Biome was null.");
@ -69,6 +72,11 @@ public class UniBiomeIslandGeneratorTest {
}
}
@BeforeAll
public void setup() {
biomeSelector.initialize();
}
@BeforeEach
public void individualSetup() {
blockValueCache = new Cache<>(524288);