Now generating primitive islands.

Favourable island mapper values.
This commit is contained in:
Harrison Deng 2020-05-01 18:37:06 -05:00
parent e4597538b2
commit a6cebe703b
7 changed files with 184 additions and 26 deletions

View File

@ -11,7 +11,7 @@ import ca.recrown.islandsurvivalcraft.Utilities;
public class BiomeSelector {
private boolean initialized = false;
private final Random random;
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<>();
@ -229,4 +229,8 @@ public class BiomeSelector {
return biomes.get((int) random.nextFloat() * biomes.size());
}
public void setBiome(Random random) {
this.random = random;
}
}

View File

@ -14,14 +14,14 @@ public class IslandWorldMapper implements CoordinateValidatable {
private final SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 8;
private final float islandBlockGenerationPercent = 10;
private final float islandBlockGenerationPercent = 15f;
private final float exaggerationFactor = 1f;
private final float islandExaggeration = 1.6f;
private final double noiseFrequency = 1.8D;
private final float islandExaggeration = 1f;
private final double noiseFrequency = 1.7D;
private final double noiseAmplitude = 0.5D;
private final double scale = 0.01D;
private final float shoreFactor = 0.035f;
private final float shallowPortion = 0.015f;
private final double scale = 0.004D;
private final float shoreFactor = 0.05f;
private final float shallowPortion = 0.01f;
private final DepthFirstSearch dfs;
public IslandWorldMapper(long seed, Cache<Point2, Double> blockValueCache) {
@ -32,7 +32,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
}
/**
* Considers this land if it is greater than sea level.
* Considers this land if the world block value is greater than or equal to 0.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return
@ -116,17 +116,16 @@ public class IslandWorldMapper implements CoordinateValidatable {
Double res = blockValueCache.get(p);
if (res == null) {
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
double shift = (portionSea - 1d) / 2d;
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);
noise = ( - Math.pow(- rawNoise, exaggerationFactor) - shift);
} else {
noise = Math.pow(rawNoise, islandExaggeration) + shift;
noise = Math.pow(rawNoise, islandExaggeration) - shift;
}
double maxNeg = -1 + shift;
double maxPos = 1 + shift;
double maxNeg = -1 - shift;
double maxPos = 1 - shift;
if (noise < 0) {
res = - noise / maxNeg;

View File

@ -23,6 +23,7 @@ import ca.recrown.islandsurvivalcraft.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
import ca.recrown.islandsurvivalcraft.world.shaders.WorldLayerShader;
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
private final Cache<Point2, Double> blockValueCache = new Cache<>(131072);
@ -41,13 +42,15 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
this.currentWorld = world;
long seed = world.getSeed();
int maxHeight = world.getMaxHeight();
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);
WorldLayerShader layerShader = new WorldLayerShader(seed, seaLevel, maxHeight, mapper);
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
biomeSelector.setBiome(random);
int maxHeight = world.getMaxHeight();
int seaLevel = world.getSeaLevel();
LinkedList<Future<Boolean>> tasks = new LinkedList<>();
ChunkData chunkData = createChunkData(world);
Future<Boolean> preLoader = exAlpha.submit(() -> {
@ -79,11 +82,25 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
final int worldX = Utilities.CHUNK_SIZE * chunkX + localX;
final int worldZ = Utilities.CHUNK_SIZE * chunkZ + localZ;
int terrainHeight = heightShader.getTerrainHeight(worldX, worldZ, biomes[localX][localZ]);
int bedrockHeight = random.nextInt(5);
chunkData.setRegion(localX, bedrockHeight, localZ, localX + 1, terrainHeight, localZ + 1, Material.STONE);
Biome currentBiome = biomes[localX][localZ];
int terrainHeight = heightShader.getTerrainHeight(worldX, worldZ, currentBiome);
int currentTerrainHeight = terrainHeight - 1;
int bedrockHeight = random.nextInt(5) + 1;
if (layerShader.hasSpecialLayers(currentBiome)) {
Material currentMaterial = layerShader.getMaterialForHeight(worldX, worldZ, currentTerrainHeight, currentBiome);
while (currentMaterial != null) {
chunkData.setBlock(localX, currentTerrainHeight, localZ, currentMaterial);
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));
}
chunkData.setRegion(localX, bedrockHeight, localZ, localX + 1, currentTerrainHeight + 1, localZ + 1, Material.STONE);
if (terrainHeight < seaLevel) {
chunkData.setRegion(localX, terrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER);
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER);
}
chunkData.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK);
}

View File

@ -0,0 +1,21 @@
package ca.recrown.islandsurvivalcraft.world.generation.noise;
import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
public class FractalOctaveGenerator {
private final SimplexOctaveGenerator simplex;
private final double amplitude = 0.5d;
private final double frequency = 0.5d;
private final double scale = 0.05D;
public FractalOctaveGenerator(Random random) {
simplex = new SimplexOctaveGenerator(random, 4);
simplex.setScale(scale);
}
public double noise(int x, int y) {
return 1d - Math.abs(simplex.noise(x, y, frequency, amplitude, true));
}
}

View File

@ -23,9 +23,9 @@ public class WorldHeightShader {
if (biomeName.contains("hills")) {
height = calculateTerrainHeight(worldX, worldZ, 15);
} else if (biomeName.contains("mountains")) {
height = calculateTerrainHeight(worldX, worldZ, 30);
height = calculateTerrainHeight(worldX, worldZ, 120);
} else if (biomeName.contains("plateau")) {
height = Math.min(calculateTerrainHeight(worldX, worldZ, 70), 40);
height = Math.min(calculateTerrainHeight(worldX, worldZ, 50), 30);
} else if (biomeName.contains("modified")) {
height = calculateTerrainHeight(worldX, worldZ, 45);
} else if (biomeName.contains("shattered")) {
@ -44,9 +44,13 @@ public class WorldHeightShader {
}
private int calculateTerrainHeight(int worldX, int worldZ, int multiplier) {
int blockHeight = seaLevel;
int blockHeight = 0;
double islandValue = islandLocator.getWorldValue(worldX, worldZ);
blockHeight += islandValue * multiplier;
if (!islandLocator.isLand(worldX, worldZ)) {
blockHeight = (int) ((1d + islandValue) * (double) multiplier);
} else {
blockHeight += seaLevel + islandValue * multiplier;
}
return blockHeight;
}
}

View File

@ -0,0 +1,101 @@
package ca.recrown.islandsurvivalcraft.world.shaders;
import java.util.Random;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class WorldLayerShader {
private final IslandWorldMapper mapper;
private final long seed;
private final int seaLevel, maxHeight;
private final Random random = new Random();
private final Material[] badlandTerrocota = new Material[] {
Material.BLACK_TERRACOTTA,
Material.BROWN_TERRACOTTA,
Material.GRAY_TERRACOTTA,
Material.LIGHT_GRAY_TERRACOTTA,
Material.YELLOW_TERRACOTTA,
Material.WHITE_TERRACOTTA
};
public WorldLayerShader(long seed, int seaLevel, int maxHeight, IslandWorldMapper mapper) {
this.mapper = mapper;
this.seed = seed;
this.seaLevel = seaLevel;
this.maxHeight = maxHeight;
}
public boolean hasSpecialLayers(Biome biome) {
String biomeName = biome.toString().toLowerCase();
return
biomeName.contains("badlands");
}
/**
* Figures out the type of material to be placed at a given world coordinate set.
*
* @param worldX The world x coordinate.
* @param worldZ The world z coordinate.
* @param y The elevation.
* @param biome the associated biome.
* @return the material for the layer. Returning null means no more special layers.
*/
public Material getMaterialForHeight(int worldX, int worldZ, int y, Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("badlands")) {
int seedOffset = (worldX + worldZ) / 1000;
random.setSeed(seed + seedOffset);
int yInterval = (int) (random.nextFloat() * 9) + 1;
int yBandInitial = (int) (seaLevel + random.nextDouble() * 50);
if (y > yBandInitial) {
int amountOfLayers = Math.min(Math.max(1, random.nextInt(badlandTerrocota.length)), 4);
int subLayer = y % yInterval;
if (subLayer < amountOfLayers) {
Material[] selected = new Material[amountOfLayers];
for (int i = 0; i < selected.length; i++) {
selected[i] = badlandTerrocota[random.nextInt(amountOfLayers)];
}
return selected[subLayer];
} else {
return Material.TERRACOTTA;
}
}
}
return null;
}
public Material getSurfaceMaterial(Biome biome) {
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")) {
return Material.GRAVEL;
} else if (biomeName.contains("stone")) {
return Material.STONE;
}
return Material.GRASS_BLOCK;
}
public Material getTransitionMaterial(Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert")) {
return Material.SANDSTONE;
} else if (biomeName.contains("stone")) {
return Material.STONE;
} else if (biomeName.contains("ocean")) {
return Material.GRAVEL;
}
return Material.DIRT;
}
public int getTransitionMaterialThickness(int worldX, int worldZ, Biome biome) {
return (int) (Math.abs(mapper.getWorldValue(worldX, worldZ)) * 10) + 6;
}
}

View File

@ -1,6 +1,7 @@
package ca.recrown.islandsurvivalcraft.world;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
@ -29,11 +30,11 @@ public class IslandWorldMapperTest {
answers[x][y] = mapper.getWorldValue(x, y);
}
}
blockValCache.clearCache();
}
@AfterEach
public void individualCleanup() {
random.setSeed(SEED);
blockValCache.clearCache();
}
@ -60,4 +61,15 @@ public class IslandWorldMapperTest {
assertEquals(answers[x][y], mapper.getWorldValue(x, y), String.format("Occurred at (%d, %d)", x, y));
}
}
@Test
public void test2048ValuesValidity() {
for (int i = 0; i < 1024; i++) {
int x = random.nextInt();
int y = random.nextInt();
assertTrue((mapper.getWorldValue(x, y) <= 1));
assertTrue((mapper.getWorldValue(x, y) >= -1));
}
}
}