From e4597538b2e07795a7311b7e60297b5b79e3ff79 Mon Sep 17 00:00:00 2001 From: Harrison Date: Thu, 30 Apr 2020 21:42:03 -0500 Subject: [PATCH] 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. --- .../world/IslandWorldMapper.java | 30 ++--- .../generation/IslandWorldChunkGenerator.java | 35 +++-- .../world/shaders/WorldHeightShader.java | 59 +++------ .../world/IslandWorldMapperTest.java | 10 +- .../UniBiomeIslandGeneratorTest.java | 123 ++++++++++++++++-- 5 files changed, 173 insertions(+), 84 deletions(-) diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java index ac8fa15..fb63c36 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapper.java @@ -13,20 +13,20 @@ public class IslandWorldMapper implements CoordinateValidatable { private final Cache 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 blockValueCache) { + public IslandWorldMapper(long seed, Cache 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; diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java index b02b4aa..5e3bc74 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/IslandWorldChunkGenerator.java @@ -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 blockValueCache = new Cache<>(262144); - private final Cache biomeCache = new Cache<>(262144); - private final Cache chunkExistenceCache = new Cache<>(32768); + private final Cache blockValueCache = new Cache<>(131072); + private final Cache biomeCache = new Cache<>(131072); + private final Cache 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> tasks = new LinkedList<>(); ChunkData chunkData = createChunkData(world); Future 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; diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java index 8690b3f..eab791d 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/shaders/WorldHeightShader.java @@ -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; } } \ No newline at end of file diff --git a/src/test/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapperTest.java b/src/test/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapperTest.java index f5463e9..3984c44 100644 --- a/src/test/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapperTest.java +++ b/src/test/java/ca/recrown/islandsurvivalcraft/world/IslandWorldMapperTest.java @@ -19,14 +19,14 @@ public class IslandWorldMapperTest { public final Cache 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)); } } } \ No newline at end of file diff --git a/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java b/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java index 4afdbd0..a5c682f 100644 --- a/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java +++ b/src/test/java/ca/recrown/islandsurvivalcraft/world/generation/UniBiomeIslandGeneratorTest.java @@ -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 blockValueCache = new Cache<>(524288); - private final Cache biomeCache = new Cache<>(524288); - private final Cache chunkExistenceCache = new Cache<>(16384); + private volatile Cache blockValueCache; + private volatile Cache biomeCache; + private volatile Cache 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> 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> 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> 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()); + } + } + } } \ No newline at end of file