Chunk generation now generates sea and island.

Adjusted values for the island mapper.
Island world mapper now takes a seed instead of a random object.
Refactoring of method names.

Reduced cache sizes used by terrain generation.

Reworked height shader to not use it's own noise generator.

Updated tests implementing changes respectively.
This commit is contained in:
Harrison Deng 2020-04-30 21:42:03 -05:00
parent d0701ce63d
commit e4597538b2
5 changed files with 173 additions and 84 deletions

View File

@ -13,20 +13,20 @@ public class IslandWorldMapper implements CoordinateValidatable {
private final Cache<Point2, Double> blockValueCache;
private final SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 4;
private final float islandBlockGenerationPercent = 16;
private final float exaggerationFactor = 1.2f;
private final float islandValueExaggerationFactor = 0.2f;
private final double noiseFrequency = 1.95D;
private final int noiseOctaves = 8;
private final float islandBlockGenerationPercent = 10;
private final float exaggerationFactor = 1f;
private final float islandExaggeration = 1.6f;
private final double noiseFrequency = 1.8D;
private final double noiseAmplitude = 0.5D;
private final double scale = 0.03D;
private final double scale = 0.01D;
private final float shoreFactor = 0.035f;
private final float shallowPortion = 0.015f;
private final DepthFirstSearch dfs;
public IslandWorldMapper(Random random, Cache<Point2, Double> blockValueCache) {
public IslandWorldMapper(long seed, Cache<Point2, Double> blockValueCache) {
dfs = new DepthFirstSearch(this);
this.noiseGenerator = new SimplexOctaveGenerator(random, noiseOctaves);
this.noiseGenerator = new SimplexOctaveGenerator(new Random(seed), noiseOctaves);
noiseGenerator.setScale(scale);
this.blockValueCache = blockValueCache;
}
@ -38,7 +38,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
* @return
*/
public boolean isLand(int worldX, int worldZ) {
if (getWorldBlockValue(worldX, worldZ) >= 0) {
if (getWorldValue(worldX, worldZ) >= 0) {
return true;
}
return false;
@ -68,7 +68,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
*/
public boolean isShore(int worldX, int worldZ) {
if (!isIsland(worldX, worldZ)) return false;
if (isLand(worldX, worldZ) && getWorldBlockValue(worldX, worldZ) <= shoreFactor) {
if (isLand(worldX, worldZ) && getWorldValue(worldX, worldZ) <= shoreFactor) {
return true;
}
return false;
@ -95,7 +95,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
* @return true if it is considered the shallow portion.
*/
public boolean isShallowPortion(int worldX, int worldZ) {
if (!isLand(worldX, worldZ) && getWorldBlockValue(worldX, worldZ) >= -shallowPortion) {
if (!isLand(worldX, worldZ) && getWorldValue(worldX, worldZ) >= -shallowPortion) {
return true;
}
return false;
@ -106,24 +106,24 @@ public class IslandWorldMapper implements CoordinateValidatable {
* World block value will be 0 or positive if it is a part of an island.
* If less than, it is considered under the sea.
* Does not factor in a shallow depth.
* The value is normalized to [0, 1].
* The value is normalized to [-1, 1].
* @param worldX the x world coordinate to obtain this value for.
* @param worldZ the z world coordinate to obtain this value for.
* @return a value representing the island at the given point.
*/
public double getWorldBlockValue(int worldX, int worldZ) {
public double getWorldValue(int worldX, int worldZ) {
Point2 p = new Point2(worldX, worldZ);
Double res = blockValueCache.get(p);
if (res == null) {
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
double shift = 1f - 2 * portionSea;
double shift = (portionSea - 1d) / 2d;
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, islandValueExaggerationFactor) + shift;
noise = Math.pow(rawNoise, islandExaggeration) + shift;
}
double maxNeg = -1 + shift;
double maxPos = 1 + shift;

View File

@ -7,6 +7,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
@ -24,9 +25,9 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
private final Cache<Point2, Double> blockValueCache = new Cache<>(262144);
private final Cache<Point2, Biome[]> biomeCache = new Cache<>(262144);
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(32768);
private final Cache<Point2, Double> blockValueCache = new Cache<>(131072);
private final Cache<Point2, Biome[]> biomeCache = new Cache<>(131072);
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(131072);
private final BiomeSelector biomeSelector = new BiomeSelector();
private final ExecutorService exAlpha = Utilities.ISC_EXECUTOR_ALPHA;
private final ExecutorService exBeta = Utilities.ISC_EXECUTOR_BETA;
@ -39,19 +40,21 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
@Override
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
this.currentWorld = world;
IslandWorldMapper mapper = new IslandWorldMapper(random, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(world.getSeed());
WorldHeightShader heightShader = new WorldHeightShader(random, mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
long seed = world.getSeed();
IslandWorldMapper mapper = new IslandWorldMapper(seed, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(seed);
WorldHeightShader heightShader = new WorldHeightShader(mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
int maxHeight = world.getMaxHeight();
int seaLevel = world.getSeaLevel();
LinkedList<Future<Boolean>> tasks = new LinkedList<>();
ChunkData chunkData = createChunkData(world);
Future<Boolean> preLoader = exAlpha.submit(() -> {
for (int x = Utilities.CHUNK_SIZE - 1; x >= 0; x--) {
for (int z = Utilities.CHUNK_SIZE - 1; z >= 0; z--) {
if (Thread.currentThread().isInterrupted()) return false;
mapper.getWorldBlockValue(Utilities.CHUNK_SIZE * chunkX + x, Utilities.CHUNK_SIZE * chunkZ + z);
mapper.getWorldValue(Utilities.CHUNK_SIZE * chunkX + x, Utilities.CHUNK_SIZE * chunkZ + z);
}
}
return true;
@ -76,11 +79,15 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
final int worldX = Utilities.CHUNK_SIZE * chunkX + localX;
final int worldZ = Utilities.CHUNK_SIZE * chunkZ + localZ;
int height = heightShader.getAltitude(worldX, worldZ, biomes[localX][localZ]);
chunkData.setRegion(localX, 1, localZ, localX + 1, height, localZ + 1, Material.DIAMOND_BLOCK);
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);
if (terrainHeight < seaLevel) {
chunkData.setRegion(localX, terrainHeight, localZ, localX + 1, seaLevel, localZ + 1, Material.WATER);
}
chunkData.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK);
}
}
chunkData.setRegion(0, 0, 0, 16, 1, 16, Material.BEDROCK);
preLoader.cancel(false);
try {
@ -93,12 +100,14 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
}
return chunkData;
}
@Override
public boolean canSpawn(World world, int x, int z) {
return super.canSpawn(world, x, z);
public Location getFixedSpawnLocation(World world, Random random) {
Location location = new Location(world, 0, 128, 0);
return location;
}
@Override
public boolean isParallelCapable() {
return true;

View File

@ -1,79 +1,52 @@
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 SimplexOctaveGenerator noiseGenerator;
private final IslandWorldMapper islandLocator;
private final int seaLevel;
private final int worldHeight;
private final int minimumHeight;
private final float probabilityOfAdditive = 0.75f;
public WorldHeightShader(Random random, IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) {
noiseGenerator = new SimplexOctaveGenerator(random, 2);
noiseGenerator.setScale(0.075D);
public WorldHeightShader(IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) {
this.islandLocator = islandLocator;
this.seaLevel = seaLevel;
this.worldHeight = worldHeight;
this.minimumHeight = minimumHeight;
}
public int getAltitude(int worldX, int worldZ, Biome biome) {
double modifier = getHeightModifier(worldX, worldZ);
int baseValue = calculateTerrainHeight(worldX, worldZ);
public int getTerrainHeight(int worldX, int worldZ, Biome biome) {
int height = 0;
String biomeName = biome.name().toLowerCase();
if (biomeName.contains("hills")) {
height = (int) (modifier * 20D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 15);
} else if (biomeName.contains("mountains")) {
height = (int) (modifier * 45D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 30);
} else if (biomeName.contains("plateau")) {
height = (int) (Math.min(60, Math.max(2, modifier * 80D)) + baseValue);
height = Math.min(calculateTerrainHeight(worldX, worldZ, 70), 40);
} else if (biomeName.contains("modified")) {
height = (int) (modifier * 25D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 45);
} else if (biomeName.contains("shattered")) {
height = (int) (modifier * 35D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 60);
} else if (biomeName.contains("tall")) {
height = (int) (modifier * 30D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 45);
} else if (!biomeName.contains("ocean")) {
height = (int) (getNormalizedHeightModifier(worldX, worldZ) * 15D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, 15);
} else {
height = (int) (getNormalizedHeightModifier(worldX, worldZ) * 5D + baseValue);
height = calculateTerrainHeight(worldX, worldZ, seaLevel);
}
if (height > worldHeight) throw new IllegalStateException("Resulting height is greater than world height! Biome this occurred on: " + biomeName);
height = Math.max(minimumHeight, height);
return height;
}
private int calculateTerrainHeight(int worldX, int worldZ) {
double islandValue = islandLocator.getWorldBlockValue(worldX, worldZ) + 1D;
islandValue *= worldHeight/4;
return (int) Math.max(Math.min(seaLevel, islandValue), minimumHeight);
}
private double getHeightModifier(int worldX, int worldZ) {
return (2 * probabilityOfAdditive - noiseGenerator.noise(worldX, worldZ, 0.5D, 0.5D, true));
}
private double maxHeightModifier() {
return 2 * probabilityOfAdditive + 1;
}
private double minHeightModifier() {
return 2 * probabilityOfAdditive - 1;
}
private double getNormalizedHeightModifier(int worldX, int worldZ) {
double heightModifier = getHeightModifier(worldX, worldZ);
if (heightModifier > 0) {
return heightModifier / maxHeightModifier();
}
return - heightModifier / minHeightModifier();
private int calculateTerrainHeight(int worldX, int worldZ, int multiplier) {
int blockHeight = seaLevel;
double islandValue = islandLocator.getWorldValue(worldX, worldZ);
blockHeight += islandValue * multiplier;
return blockHeight;
}
}

View File

@ -19,14 +19,14 @@ public class IslandWorldMapperTest {
public final Cache<Point2, Double> blockValCache = new Cache<>(2048);
public final int SEED = 102385923;
public Random random = new Random(SEED);
public IslandWorldMapper mapper = new IslandWorldMapper(random, blockValCache);
public IslandWorldMapper mapper = new IslandWorldMapper(SEED, blockValCache);
public final double[][] answers = new double[2048][2048];
@BeforeAll
public void setUp() {
for (int x = 0; x < answers.length; x++) {
for (int y = 0; y < answers[x].length; y++) {
answers[x][y] = mapper.getWorldBlockValue(x, y);
answers[x][y] = mapper.getWorldValue(x, y);
}
}
blockValCache.clearCache();
@ -40,14 +40,14 @@ public class IslandWorldMapperTest {
@BeforeEach
public void individualSetUp() {
random = new Random(SEED);
mapper = new IslandWorldMapper(random, blockValCache);
mapper = new IslandWorldMapper(SEED, blockValCache);
}
@Test
public void testBlockValueConsistency() {
for (int x = 0; x < answers.length; x++) {
for (int y = 0; y < answers[x].length; y++) {
assertEquals(answers[x][y], mapper.getWorldBlockValue(x, y), String.format("Occurred at (%d, %d)", x, y));
assertEquals(answers[x][y], mapper.getWorldValue(x, y), String.format("Occurred at (%d, %d)", x, y));
}
}
}
@ -57,7 +57,7 @@ public class IslandWorldMapperTest {
for (int amount = 0; amount < 1024; amount++) {
int x = random.nextInt(answers.length);
int y = random.nextInt(answers[x].length);
assertEquals(answers[x][y], mapper.getWorldBlockValue(x, y), String.format("Occurred at (%d, %d)", x, y));
assertEquals(answers[x][y], mapper.getWorldValue(x, y), String.format("Occurred at (%d, %d)", x, y));
}
}
}

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;
@ -12,6 +11,7 @@ import java.util.concurrent.TimeUnit;
import org.bukkit.block.Biome;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.Timeout;
@ -27,9 +27,9 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class UniBiomeIslandGeneratorTest {
private final int SEED = 249398015;
private final DummyWorld dummyWorld = new DummyWorld();
private final Cache<Point2, Double> blockValueCache = new Cache<>(524288);
private final Cache<Point2, Biome[]> biomeCache = new Cache<>(524288);
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
private volatile Cache<Point2, Double> blockValueCache;
private volatile Cache<Point2, Biome[]> biomeCache;
private volatile Cache<Point2, Boolean> chunkExistenceCache;
private class BiomeGenTask implements Runnable {
private final int amount;
@ -49,8 +49,7 @@ public class UniBiomeIslandGeneratorTest {
}
public void generateBiome(int chunkX, int chunkZ) {
Random rand = new Random(SEED);
IslandWorldMapper mapper = new IslandWorldMapper(rand, blockValueCache);
IslandWorldMapper mapper = new IslandWorldMapper(SEED, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED);
BiomeSelector biomeSelector = new BiomeSelector();
biomeSelector.initialize();
@ -70,6 +69,13 @@ public class UniBiomeIslandGeneratorTest {
}
}
@BeforeEach
public void individualSetup() {
blockValueCache = new Cache<>(524288);
biomeCache = new Cache<>(524288);
chunkExistenceCache = new Cache<>(16384);
}
@AfterEach
public void individualCleanup() {
blockValueCache.clearCache();
@ -107,6 +113,40 @@ public class UniBiomeIslandGeneratorTest {
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread1608ChunksSmallCache() {
this.blockValueCache = new Cache<>(1024);
this.biomeCache = new Cache<>(1024);
this.chunkExistenceCache = new Cache<>(1024);
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 1);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 4);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 5);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationSingleThread1608Chunks() {
@ -146,8 +186,11 @@ public class UniBiomeIslandGeneratorTest {
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread6000ChunksScatteredColumns() {
int chunksToDoEach = 1000;
public void testBiomeGenerationMultithread1608ChunksScatteredColumnsSmallCache() {
this.blockValueCache = new Cache<>(1024);
this.biomeCache = new Cache<>(1024);
this.chunkExistenceCache = new Cache<>(1024);
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 4);
@ -173,4 +216,68 @@ public class UniBiomeIslandGeneratorTest {
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread6000ChunksScatteredColumns() {
int chunksToDoEach = 1000;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread6000ChunksScatteredColumnsSmallCache() {
this.blockValueCache = new Cache<>(1024);
this.biomeCache = new Cache<>(1024);
this.chunkExistenceCache = new Cache<>(1024);
int chunksToDoEach = 1000;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
}