World generation feels more natural now.

Mountain tips after a certain height is stone.

Deep ocean biomes are now actually deeper.

Temperature map no longer calculates in groups of 4.
This commit is contained in:
Harrison Deng 2020-05-03 16:44:43 -05:00
parent 085827264b
commit 740ca812ae
8 changed files with 72 additions and 44 deletions

View File

@ -20,6 +20,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
private final float shoreFactor = 0.095f;
private final float shallowPortion = 0.07f;
private final double scale = 0.005D;
private final double deepOceanPortion = 0.6d;
private final DepthFirstSearch dfs;
public IslandWorldMapper(long seed, Cache<Point2, Double> blockValueCache) {
@ -150,6 +151,10 @@ public class IslandWorldMapper implements CoordinateValidatable {
return res;
}
public boolean isDeepOcean(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) <= -deepOceanPortion;
}
@Override
public boolean validate(int x, int y) {
return isIsland(x, y);

View File

@ -1,7 +1,5 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.block.Biome;
@ -28,7 +26,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.
* @param seed The seed to use for the biome for this column.
*/
public void generateBiomeColumn(Biome[][][] chunkBiomeSets, 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);
public void generateBiomeColumn(Biome[][][] chunkBiomeSets, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache<Point2, Biome[]> biomeCache, Cache<Point2, Boolean> chunkGenCache, long seed);
}

View File

@ -72,7 +72,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
final int worldZ = Utilities.CHUNK_SIZE * chunkZ + localZ;
if (biomeSet[localX][localZ][0] == null) {
biomeGenerator.generateBiomeColumn(biomeSet, world, chunkX, chunkZ, localX, localZ, mapper, biomeSelector,
temperatureMapGenerator, biomeCache, chunkExistenceCache, new Random(seed + worldX + worldZ));
temperatureMapGenerator, biomeCache, chunkExistenceCache, seed);
}
if (biomeSet[localX][localZ][0] == null) throw new IllegalStateException("Biome was null.");
tasks.add(exBeta.submit(() -> {
@ -87,11 +87,11 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
int currentTerrainHeight = terrainHeight - 1;
int bedrockHeight = random.nextInt(5) + 1;
if (layerShader.hasSpecialLayers(currentBiomeSet[0])) {
BlockData currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiomeSet[0]);
BlockData currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiomeSet);
while (currentMaterial != null) {
chunkData.setBlock(localX, currentTerrainHeight, localZ, currentMaterial);
currentTerrainHeight--;
currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiomeSet[0]);
currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiomeSet);
}
} else {
int surfaceThickness = layerShader.getSurfaceThickness(worldX, worldZ, currentBiomeSet[0]);

View File

@ -29,7 +29,7 @@ class TemperatureMapGenerator {
}
public float getTemperature(int worldX, int worldZ) {
Point2 loc = new Point2(worldX/4, worldZ/4);
Point2 loc = new Point2(worldX, worldZ);
Float val = temperatureCache.get(loc);
if (val == null) {
val = (float) noiseGenerator.noise(worldX, worldZ, frequency, amplitude, true);

View File

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

View File

@ -27,13 +27,11 @@ public class WorldHeightShader {
int height = 0;
String biomeName = biomeSet[0].name().toLowerCase();
if (!mapper.isLand(worldX, worldZ)) {
if (biomeName.contains("deep")) {
height = (int) calculateTerrainFactor(worldX, worldZ, seaLevel * 0.9d, 1.5d, 0.5d, 1d, 0.125d);
} else {
height = (int) calculateTerrainFactor(worldX, worldZ, seaLevel * 0.7d, 1.5d, 0.5d, 1d, 0.15d);
}
} else if (biomeName.contains("shore") || biomeName.contains("beach")) {
height = (int) calculateTerrainFactor(worldX, worldZ, seaLevel * 0.8d, 1.7d, 0.5d, 1d, 0.1d);
} else if (biomeName.contains("beach")) {
height = (int) (getIslandBiomeHeight(worldX, worldZ, biomeSet[1], 5d));
} else if (biomeName.contains("shore")) {
height = (int) (getIslandBiomeHeight(worldX, worldZ, biomeSet[1], 20d));
} else {
height = getIslandBiomeHeight(worldX, worldZ, biomeSet[0], 0d) + 1;
}

View File

@ -6,14 +6,19 @@ import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Snowable;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.datatypes.Vector3;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class WorldLayerShader {
private final Cache<Vector3, Double> noiseCache = new Cache<>(256);
private final SimplexOctaveGenerator noise;
private final IslandWorldMapper mapper;
private final long seed;
private final int seaLevel, maxHeight;
private final Random random = new Random();
private final Random random;
private final Material[] badlandTerrocota = new Material[] {
@ -30,13 +35,17 @@ public class WorldLayerShader {
this.seed = seed;
this.seaLevel = seaLevel;
this.maxHeight = maxHeight;
random = new Random(seed);
this.noise = new SimplexOctaveGenerator(new Random(seed), 4);
this.noise.setScale(0.03d);
}
public boolean hasSpecialLayers(Biome biome) {
String biomeName = biome.toString().toLowerCase();
return
biomeName.contains("badlands") ||
biomeName.contains("snowy");
biomeName.contains("snowy") ||
biomeName.contains("mountain");
}
/**
@ -45,12 +54,15 @@ public class WorldLayerShader {
* @param worldX The world x coordinate.
* @param worldZ The world z coordinate.
* @param y The elevation.
* @param biome the associated biome.
* @param biomeSet the associated biome.
* @return the material for the layer. Returning null means no more special layers.
*/
public BlockData getMaterialForHeight(int worldX, int worldZ, int y, int highestPoint, Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("badlands")) {
public BlockData getMaterialForHeight(int worldX, int worldZ, int y, int highestPoint, Biome[] biomeSet) {
Biome mainBiome = biomeSet[0];
String mainBiomeName = mainBiome.toString().toLowerCase();
BlockData res = null;
if (mainBiomeName.contains("badlands")) {
int seedOffset = (worldX + worldZ) / 1000;
random.setSeed(seed + seedOffset);
int yInterval = (int) (random.nextFloat() * 9) + 1;
@ -64,27 +76,36 @@ public class WorldLayerShader {
for (int i = 0; i < selected.length; i++) {
selected[i] = badlandTerrocota[random.nextInt(amountOfLayers)];
}
return selected[subLayer].createBlockData();
res = selected[subLayer].createBlockData();
} else {
return Material.TERRACOTTA.createBlockData();
res = Material.TERRACOTTA.createBlockData();
}
}
} else if (biomeName.contains("snowy")) {
} else if (mainBiomeName.contains("mountain")) {
if (y > (maxHeight * 0.48d) - getNormalizedNoise(worldX, worldZ, 2f) * 6d) {
res = Material.STONE.createBlockData();
} else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome)) {
res = getSurfaceMaterial(mainBiome).createBlockData();
} else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome) - getTransitionMaterialThickness(worldX, worldZ, mainBiome)) {
res = getTransitionMaterial(mainBiome).createBlockData();
}
}
if (mainBiomeName.contains("snowy")) {
if (highestPoint == y) {
return Material.SNOW.createBlockData();
} else if (y > highestPoint - 1 - getSurfaceThickness(worldX, worldZ, biome)) {
Material surfaceMaterial = getSurfaceMaterial(biome);
BlockData res = surfaceMaterial.createBlockData();
if (getSurfaceMaterial(biome) == Material.GRASS_BLOCK) {
res = Material.SNOW.createBlockData();
} else if (y > highestPoint - 1 - getSurfaceThickness(worldX, worldZ, mainBiome)) {
if (res == null || res.getMaterial() == Material.DIRT) res = getSurfaceMaterial(mainBiome).createBlockData();
if (res.getMaterial() == Material.GRASS_BLOCK) {
Snowable snowable = (Snowable) res;
snowable.setSnowy(true);
}
return res;
} else if (y > highestPoint - 1 - getSurfaceThickness(worldX, worldZ, biome) - getTransitionMaterialThickness(worldX, worldZ, biome)) {
return getTransitionMaterial(biome).createBlockData();
} else if (y > highestPoint - 1 - getSurfaceThickness(worldX, worldZ, mainBiome) - getTransitionMaterialThickness(worldX, worldZ, mainBiome)) {
if (res == null) res = getTransitionMaterial(mainBiome).createBlockData();
}
}
return null;
return res;
}
public Material getSurfaceMaterial(Biome biome) {
@ -105,7 +126,7 @@ public class WorldLayerShader {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach")) {
return (int) (Math.abs(mapper.getWorldValue(worldX, worldZ)) * 5) + 4;
return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 5) + 4;
}
return 1;
@ -115,7 +136,7 @@ public class WorldLayerShader {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert")) {
return Material.SANDSTONE;
} else if (biomeName.contains("stone")) {
} else if (biome == Biome.STONE_SHORE) {
return Material.STONE;
} else if (biomeName.contains("ocean")) {
return Material.GRAVEL;
@ -124,6 +145,16 @@ public class WorldLayerShader {
}
public int getTransitionMaterialThickness(int worldX, int worldZ, Biome biome) {
return (int) (Math.abs(mapper.getWorldValue(worldX, worldZ)) * 4) + 4;
return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 4) + 4;
}
private double getNormalizedNoise(int worldX, int worldZ, float freq) {
Vector3 key = new Vector3(worldX, worldZ, freq);
Double res = noiseCache.get(key);
if (res == null) {
res = (noise.noise(worldX, worldZ, freq, 0.5d) + 1d) / 2d;
noiseCache.set(key, res);
}
return res;
}
}

View File

@ -3,7 +3,6 @@ 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;
@ -33,7 +32,6 @@ public class UniBiomeIslandGeneratorTest {
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 {
@ -63,7 +61,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, random);
biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache, SEED);
}
if (biomes[localX][localZ] == null)
throw new IllegalStateException("Biome was null.");