From ff55368db26ac48062598918ee184b585ccea2d6 Mon Sep 17 00:00:00 2001 From: Forwolk Date: Wed, 1 Aug 2018 10:43:37 +0300 Subject: [PATCH] WorldGenerator gen 1 --- .../main/java/mc/core/block/BlockType.java | 8 +- core/src/main/java/mc/core/world/Biome.java | 52 +-- core/src/main/java/mc/core/world/Region.java | 1 - core/src/main/java/mc/core/world/World.java | 5 +- .../main/java/mc/world/flat/FlatWorld.java | 17 +- .../mc/world/generated_world/CubicWorld.java | 89 ++++ .../SeedBasedWorldGenerator.java | 390 ++++++++++++++++++ .../generated_world/SeedRandomGenerator.java | 2 +- .../world/generated_world/WorldConstants.java | 17 + .../generated_world/chunk/ChunkProxy.java | 128 ++++++ .../chunk/ProxiedChunkLoader.java | 9 + .../generated_world/region/RegionImpl.java | 63 +++ .../generated_world/word/Temperature.java | 9 + .../world/generated_world/word/Wetness.java | 9 + .../SeedRandomGeneratorTest.java | 34 ++ 15 files changed, 799 insertions(+), 34 deletions(-) create mode 100644 generated_world/src/main/java/mc/world/generated_world/CubicWorld.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/SeedBasedWorldGenerator.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/WorldConstants.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/chunk/ProxiedChunkLoader.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/word/Temperature.java create mode 100644 generated_world/src/main/java/mc/world/generated_world/word/Wetness.java diff --git a/core/src/main/java/mc/core/block/BlockType.java b/core/src/main/java/mc/core/block/BlockType.java index cf6cc1a..f4a8648 100644 --- a/core/src/main/java/mc/core/block/BlockType.java +++ b/core/src/main/java/mc/core/block/BlockType.java @@ -3,9 +3,13 @@ package mc.core.block; import lombok.Getter; public enum BlockType { - DIRT(1, "Dirt"), + STONE(1, "Stone"), GRASS(2, "Grass"), - BEDROCK(7, "Bedrock"); + DIRT(3, "Dirt"), + BEDROCK(7, "Bedrock"), + WATER(8, "Water"), + SAND(12, "Sand"), + SNOW(32, "Snow"); @Getter private final int id; diff --git a/core/src/main/java/mc/core/world/Biome.java b/core/src/main/java/mc/core/world/Biome.java index ef59d8f..9826a68 100644 --- a/core/src/main/java/mc/core/world/Biome.java +++ b/core/src/main/java/mc/core/world/Biome.java @@ -3,37 +3,41 @@ package mc.core.world; import lombok.Getter; public enum Biome { - OCEAN(0, "Ocean"), - PLAINS(1, "Plains"), - DESERT(2, "Desert"), - EXTREME_HILLS(3, "Extreme hills"), - FOREST(4, "Forest"), - TAIGA(5, "Taiga"), - SWAMPLAND(6, "Swampland"), - RIVER(7, "River"), - HELL(8, "Hell"), - SKY(9, "Sky"), - FROZEN_OCEAN(10, "Frozen ocean"), - FROZEN_RIVER(11, "Frozen river"), - ICE_PLAINS(12, "Ice plains"), - ICE_MOUNTAINS(13, "Ice mountains"), - MUSHROOM_ISLAND(14, "Mushroom island"), - MUSHROOM_ISLAND_SHORE(15, "Mushroom island shore"), - BEACH(16, "Beach"), - DESERT_HILLS(17, "Desert hills"), - FOREST_HILLS(18, "Forest hills"), - TAIGA_HILLS(19, "Taiga hills"), - EXTREME_HILLS_EDGE(20, "Extreme hills edge"), - JUNGLE(21, "Jungle"), - JUNGLE_HILLS(22, "Jungle hills"); + OCEAN(0, "Ocean", 0x000080), + PLAINS(1, "Plains", 0x008000), + DESERT(2, "Desert", 0xbdb76b), + EXTREME_HILLS(3, "Extreme hills", 0xffffff), + FOREST(4, "Forest", 0x006400), + TAIGA(5, "Taiga", 0xf0f8ff), + SWAMPLAND(6, "Swampland", 0x808000), + RIVER(7, "River", 0xffffff), + HELL(8, "Hell", 0xffffff), + SKY(9, "Sky", 0xffffff), + FROZEN_OCEAN(10, "Frozen ocean", 0xe0ffff), + FROZEN_RIVER(11, "Frozen river", 0xffffff), + ICE_PLAINS(12, "Ice plains", 0xfffafa), + ICE_MOUNTAINS(13, "Ice mountains", 0xfffafa), + MUSHROOM_ISLAND(14, "Mushroom island", 0xffffff), + MUSHROOM_ISLAND_SHORE(15, "Mushroom island shore", 0xffffff), + BEACH(16, "Beach", 0xffffff), + DESERT_HILLS(17, "Desert hills", 0xbdb76b), + FOREST_HILLS(18, "Forest hills", 0x006400), + TAIGA_HILLS(19, "Taiga hills", 0xf0f8ff), + EXTREME_HILLS_EDGE(20, "Extreme hills edge", 0xffffff), + JUNGLE(21, "Jungle", 0xadff2f), + JUNGLE_HILLS(22, "Jungle hills", 0xadff2f), + DEEP_OCEAN(23, "Deep ocean", 0x191970); @Getter private final int id; @Getter private final String name; + @Getter + private final int color; - Biome(int id, String name) { + Biome(int id, String name, int color) { this.id = id; this.name = name; + this.color = color; } } diff --git a/core/src/main/java/mc/core/world/Region.java b/core/src/main/java/mc/core/world/Region.java index efee50a..11b197c 100644 --- a/core/src/main/java/mc/core/world/Region.java +++ b/core/src/main/java/mc/core/world/Region.java @@ -19,7 +19,6 @@ public interface Region { void setChunk(int x, int y, int z, Chunk chunk); int getX(); - int getY(); int getZ(); Biome getBiomeAt (int x, int z); diff --git a/core/src/main/java/mc/core/world/World.java b/core/src/main/java/mc/core/world/World.java index e0e7df2..aafdc2e 100644 --- a/core/src/main/java/mc/core/world/World.java +++ b/core/src/main/java/mc/core/world/World.java @@ -49,5 +49,8 @@ public interface World { Chunk getChunk(int x, int y, int z); void setChunk(int x, int y, int z, Chunk chunk); - long getSeed (); + Region getRegion (int x, int z); + void setRegion (int x, int z, Region region); + + int getSeed (); } diff --git a/flat_world/src/main/java/mc/world/flat/FlatWorld.java b/flat_world/src/main/java/mc/world/flat/FlatWorld.java index abeb0d0..418fbab 100644 --- a/flat_world/src/main/java/mc/world/flat/FlatWorld.java +++ b/flat_world/src/main/java/mc/world/flat/FlatWorld.java @@ -7,10 +7,7 @@ package mc.world.flat; import lombok.Getter; import lombok.Setter; import mc.core.Location; -import mc.core.world.Chunk; -import mc.core.world.IWorldType; -import mc.core.world.World; -import mc.core.world.WorldType; +import mc.core.world.*; import java.util.UUID; @@ -40,7 +37,17 @@ public class FlatWorld implements World { } @Override - public long getSeed() { + public Region getRegion(int x, int z) { + return null; + } + + @Override + public void setRegion(int x, int z, Region region) { + + } + + @Override + public int getSeed() { return 0; } } diff --git a/generated_world/src/main/java/mc/world/generated_world/CubicWorld.java b/generated_world/src/main/java/mc/world/generated_world/CubicWorld.java new file mode 100644 index 0000000..d22fed4 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/CubicWorld.java @@ -0,0 +1,89 @@ +package mc.world.generated_world; + +import lombok.Getter; +import mc.core.Location; +import mc.core.world.*; + +import java.util.UUID; + +public class CubicWorld implements World { + @Getter + private final UUID worldId; + private final int seed; + private volatile Location spawnLocation; + private final transient Object spawnLocationLock = new Object(); + private final transient ChunkLoader chunkLoader; + + public CubicWorld(UUID worldId, int seed) { + this.worldId = worldId; + chunkLoader = new InMemoryCacheChunkLoader(worldId); + this.seed = seed; + } + + public CubicWorld(int seed) { + this.worldId = UUID.randomUUID(); + chunkLoader = new InMemoryCacheChunkLoader(worldId); + this.seed = seed; + } + + public CubicWorld(UUID worldId) { + this.worldId = worldId; + chunkLoader = new InMemoryCacheChunkLoader(worldId); + this.seed = 0; + } + + public CubicWorld () { + this.worldId = UUID.randomUUID(); + chunkLoader = new InMemoryCacheChunkLoader(worldId); + this.seed = 0; + } + + @Override + public IWorldType getWorldType() { + return null; + } + + @Override + public Location getSpawn() { + if (spawnLocation == null) { + synchronized (spawnLocationLock) { + if (spawnLocation == null) { + spawnLocation = Location.startPointLocation(); + } + } + } + return spawnLocation; + } + + @Override + public void setSpawn(Location location) { + synchronized (spawnLocationLock) { + this.spawnLocation = location; + } + } + + @Override + public Chunk getChunk(int x, int y, int z) { + return null; + } + + @Override + public void setChunk(int x, int y, int z, Chunk chunk) { + + } + + @Override + public Region getRegion(int x, int z) { + return null; + } + + @Override + public void setRegion(int x, int z, Region region) { + + } + + @Override + public int getSeed() { + return seed; + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/SeedBasedWorldGenerator.java b/generated_world/src/main/java/mc/world/generated_world/SeedBasedWorldGenerator.java new file mode 100644 index 0000000..0ef4239 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/SeedBasedWorldGenerator.java @@ -0,0 +1,390 @@ +package mc.world.generated_world; + +import lombok.RequiredArgsConstructor; +import mc.core.block.BlockFactory; +import mc.core.block.BlockType; +import mc.core.world.*; +import mc.world.generated_world.region.RegionImpl; +import mc.world.generated_world.word.Temperature; +import mc.world.generated_world.word.Wetness; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.UUID; + +public class SeedBasedWorldGenerator implements WorldGenerator { + + public static void main(String[] args) throws Exception{ + WorldGenerator worldGenerator = new SeedBasedWorldGenerator(); + World world = new CubicWorld(UUID.fromString("00000000-0000-0000-C000-000000000046"), 123); + worldGenerator.generateRegion(0, 0, world); + worldGenerator.generateRegion(1, 0, world); + worldGenerator.generateRegion(-1, 0, world); + worldGenerator.generateRegion(0, 1, world); + worldGenerator.generateRegion(0, -1, world); + BufferedImage image = new BufferedImage(3 * 256, 3 * 256, BufferedImage.TYPE_INT_RGB); + BufferedImage currentImage; + int shiftX; + int shiftY; + currentImage = ImageIO.read(new File("out/0.0", "biomeMap.png")); + shiftX = 1; + shiftY = 1; + for (int x = 0; x < 256; x ++){ + for (int y = 0; y < 256; y ++){ + int tx = 256 * shiftX + x; + int ty = 256 * shiftY + y; + image.setRGB(tx, ty, currentImage.getRGB(x, y)); + } + } + currentImage = ImageIO.read(new File("out/0.1", "biomeMap.png")); + shiftX = 1; + shiftY = 2; + for (int x = 0; x < 256; x ++){ + for (int y = 0; y < 256; y ++){ + int tx = 256 * shiftX + x; + int ty = 256 * shiftY + y; + image.setRGB(tx, ty, currentImage.getRGB(x, y)); + } + } + currentImage = ImageIO.read(new File("out/1.0", "biomeMap.png")); + shiftX = 2; + shiftY = 1; + for (int x = 0; x < 256; x ++){ + for (int y = 0; y < 256; y ++){ + int tx = 256 * shiftX + x; + int ty = 256 * shiftY + y; + image.setRGB(tx, ty, currentImage.getRGB(x, y)); + } + } + currentImage = ImageIO.read(new File("out/-1.0", "biomeMap.png")); + shiftX = 0; + shiftY = 1; + for (int x = 0; x < 256; x ++){ + for (int y = 0; y < 256; y ++){ + int tx = 256 * shiftX + x; + int ty = 256 * shiftY + y; + image.setRGB(tx, ty, currentImage.getRGB(x, y)); + } + } + currentImage = ImageIO.read(new File("out/0.-1", "biomeMap.png")); + shiftX = 1; + shiftY = 0; + for (int x = 0; x < 256; x ++){ + for (int y = 0; y < 256; y ++){ + int tx = 256 * shiftX + x; + int ty = 256 * shiftY + y; + image.setRGB(tx, ty, currentImage.getRGB(x, y)); + } + } + ImageIO.write(image, "png", new File("out", "merged.png")); + } + + @Override + public Region generateRegion(int x, int z, World world) { + Region region = new RegionImpl(x,z); + RegionGenerator regionGenerator = new RegionGenerator(world, region); + regionGenerator.generate(); + return region; + } + + @RequiredArgsConstructor + private class RegionGenerator { + private final World world; + private final Region region; + private final int size = 256; + private NoiseGenerator noiseGenerator; + private BlockFactory blockFactory; + + private double sigmoid (double x) { + x -= 0.5; + x *= 15; + return 1.0 / (1.0 + Math.exp(-x)); + } + + private int convert (int x) { + return 40960 + x; + } + + public void generate() { + noiseGenerator = new NoiseGenerator(world.getSeed()); + noiseGenerator.init(); + File file = new File("out", region.getX() + "." + region.getZ()); + file.mkdirs(); + int[][] heightMap = new int[size][size]; + int[][] grassMap = new int[size][size]; + int[][] temperatureMap = new int[size][size]; + int[][] wetMap = new int[size][size]; + Biome[][] biomes = new Biome[size][size]; + for (int x = 0; x < size; x ++) { + for (int z = 0; z < size; z ++) { + int tx = convert(x + region.getX() * 256); + int tz = convert(z + region.getZ() * 256); + double p = sigmoid(noiseGenerator.noise(tx / 53d, tz / 53d)); + double r = Math.sqrt(noiseGenerator.noise(tx / 6d, tz / 6d)); + double h = (WorldConstants.WORLD_MAX_HEIGHT - WorldConstants.WORLD_MIN_HEIGHT) * Math.min(p * r, 1); + h = Math.min(WorldConstants.WORLD_MAX_HEIGHT, h + WorldConstants.WORLD_MIN_HEIGHT); + heightMap[x][z] = (int)(h); + grassMap[x][z] = (int) (1 + SeedRandomGenerator.random(tx, tz, world.getSeed()) * (WorldConstants.LANDFILL_GRASS_SURFACE_THIN - 1)); + double k = Math.sqrt(noiseGenerator.noise(tx * 2.99, tz * 2.99)); + double q = Math.sqrt(noiseGenerator.noise(tx / 41.0, tz / 41.0)); + temperatureMap[x][z] = (int) (100 * Math.min((k * k + q * q + k * q) * k * q, 0.99)); + if (heightMap[x][z] < WorldConstants.WORLD_SEA_LEVEL) { + biomes[x][z] = Biome.OCEAN; + wetMap[x][z] = 100; + } + } + } + for (int x = 1; x < size - 1; x ++) { + for (int z = 1; z < size - 1; z++) { + int mid = 0; + for (int tx = x - 1; tx <= x + 1; tx ++) { + for (int tz = z - 1; tz <= z + 1; tz ++) { + mid += wetMap[tx][tz]; + } + } + wetMap[x][z] = mid / 9; + } + } + for (int z = 1; z < size - 1; z++) { + for (int x = 1; x < size - 1; x ++) { + int mid = 0; + for (int tx = x - 1; tx <= x + 1; tx ++) { + for (int tz = z - 1; tz <= z + 1; tz ++) { + mid += wetMap[tx][tz]; + } + } + wetMap[x][z] = (int) (mid / 9 * (1 + 0.1 * SeedRandomGenerator.random(x, z, world.getSeed()))); + } + } + + for (int z = 1; z < size - 1; z++) { + for (int x = 1; x < size - 1; x ++) { + wetMap[x][z] = (int) Math.min(100, 60 * noiseGenerator.noise(x / 31d, z / 67d) + wetMap[x][z] * (1 + 0.2 * SeedRandomGenerator.random(x, z, world.getSeed()))); + } + } + + smooth(grassMap); + smooth(temperatureMap); + smooth(wetMap); + //smooth(heightMap); + + for (int x = 0; x < 256; x ++) { + for (int z = 0; z < 256; z ++) { + Temperature temperature = Temperature.values()[temperatureMap[x][z] / 20]; + Wetness wetness = Wetness.values()[(Math.min(wetMap[x][z], 100) - 1) / 100 * Wetness.values().length]; + biomes[x][z] = selectBiome(temperature, wetness, heightMap[x][z]); + } + } + + // ================================ DEBUG ======================================= + try { + BufferedImage image = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); + BufferedImage subImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < 256; x ++) { + for (int z = 0; z < 256; z ++) { + int h = heightMap[x][z]; + h = h << 16 | h << 8 | h; + image.setRGB(x, z, h); + } + } + ImageIO.write(image, "png", new File("out/" + region.getX() + "." +region.getZ() +"/hightmap.png")); + image = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < 256; x ++) { + for (int z = 0; z < 256; z ++) { + int temp = 0xff * temperatureMap[x][z] / 100; + temp = temp << 16; + image.setRGB(x, z, temp); + subImage.setRGB(x, z, (0xff * (int) (temperatureMap[x][z] / 20) / 5) << 16); + } + } + ImageIO.write(image, "png", new File("out/" + region.getX() + "." +region.getZ() + "/temperatureMap.png")); + ImageIO.write(subImage, "png", new File("out/" + region.getX() + "." +region.getZ() + "/reg_temperatureMap.png")); + image = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < 256; x ++) { + for (int z = 0; z < 256; z ++) { + int wet = 0xff * wetMap[x][z] / 100; + image.setRGB(x, z, wet); + } + } + ImageIO.write(image, "png", new File("out/" + region.getX() + "." +region.getZ() + "/wetMap.png")); + image = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < 256; x ++) { + for (int z = 0; z < 256; z ++) { + image.setRGB(x, z, biomes[x][z].getColor()); + } + } + ImageIO.write(image, "png", new File("out/" + region.getX() + "." +region.getZ() + "/biomeMap.png")); + } catch (Exception e) {} + // ================================ DEBUG FINISH ======================================= + + for (int x = 0; x < size; x ++) { + for (int z = 0; z < size; z ++) { + region.setBiome(x, z, biomes[x][z]); + if (heightMap[x][z] < WorldConstants.WORLD_SEA_LEVEL) { + for (int y = 0; y < WorldConstants.WORLD_SEA_LEVEL; y ++) { + Chunk chunk = region.getChunkAt(x / 16, y / 16, z / 16); + if (y == 0) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); + continue; + } + if (y < heightMap[x][z]) { + if (y < heightMap[x][z] - grassMap[x][z]) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); + } else { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); + } + } else { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.WATER, 0)); + } + } + } else { + for (int y = 0; y < heightMap[x][z]; y++) { + Chunk chunk = region.getChunkAt(x / 16, y / 16, z / 16); + if (y == 0) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.BEDROCK, 0)); + continue; + } + if (y < heightMap[x][z] - grassMap[x][z]) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.STONE, 0)); + } else { + if (biomes[x][z] == Biome.DESERT || biomes[x][z] == Biome.DESERT_HILLS) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.SAND, 0)); + } else if (biomes[x][z] == Biome.TAIGA || biomes[x][z] == Biome.TAIGA_HILLS) { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.DIRT, 0)); + } else { + chunk.setBlock(x % 16, y % 16, z % 16, blockFactory.create(BlockType.GRASS, 0)); + } + } + } + } + } + } + } + + private Biome selectBiome (Temperature temperature, Wetness wetness, int height) { + if (temperature == Temperature.FROST) { + if (height < WorldConstants.WORLD_SEA_LEVEL) { + return Biome.FROZEN_OCEAN; + } else { + if (height > (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MAX_HEIGHT) / 2) { + return Biome.ICE_MOUNTAINS; + } else { + return Biome.ICE_PLAINS; + } + } + } + if (height < WorldConstants.WORLD_SEA_LEVEL) { + if (height < (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MIN_HEIGHT) / 2){ + return Biome.DEEP_OCEAN; + } else { + return Biome.OCEAN; + } + } + if (temperature == Temperature.COLD) { + if (height > (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MAX_HEIGHT) / 2) { + return Biome.TAIGA_HILLS; + } else { + return Biome.TAIGA; + } + } + if (temperature == Temperature.WARM) { + if (wetness.ordinal() < 2) { + return Biome.PLAINS; + } else if (wetness == Wetness.WATER){ + return Biome.SWAMPLAND; + } else { + if (height > (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MAX_HEIGHT) / 2) { + return Biome.FOREST_HILLS; + } else { + return Biome.FOREST; + } + } + } + if (temperature == Temperature.HOTTEST && wetness.ordinal() < 2) { + if (height > (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MAX_HEIGHT) / 2) { + return Biome.DESERT_HILLS; + } else { + return Biome.DESERT; + } + } + + if (wetness.ordinal() > 2) { + if (height > (WorldConstants.WORLD_SEA_LEVEL + WorldConstants.WORLD_MAX_HEIGHT) / 2) { + return Biome.JUNGLE_HILLS; + } else { + return Biome.JUNGLE; + } + } + + + return Biome.PLAINS; + } + + private void smooth (int [][] map) { + final int[][] original = map.clone(); + for (int y = 1; y < map.length - 1; y ++) { + for (int x = 1; x < map[0].length - 1; x ++) { + int mid = 0; + for (int tx = x - 1; tx <= x + 1; tx ++) { + for (int ty = y - 1; ty <= y + 1; ty ++) { + mid += original[tx][ty]; + } + } + map[x][y] = mid / 9; + } + } + } + } + + @RequiredArgsConstructor + private class NoiseGenerator { + int size = 256; + int mask = size - 1; + int[] perm = new int[size]; + double[] gradsX = new double[size]; + double[] gradsY = new double[size]; + private final int seed; + + void init() { + for (int i = 0; i < size; ++i) { + int other = rand(i) % (i + 1); + if (i > other) + perm[i] = perm[other]; + perm[other] = i; + gradsX[i] = Math.cos(2.0f * Math.PI * i / size); + gradsY[i] = Math.sin(2.0f * Math.PI * i / size); + } + } + + double f(double t) { + t = Math.abs(t); + return t >= 1.0f ? 0.0f : 1.0f - + (3.0f - 2.0f * t) * t * t; + } + + double surflet(double x, double y, double gradX, double gradY) { + return f(x) * f(y) * (gradX * x + gradY * y); + } + + double noise(double x, double y) { + float result = 0.0f; + int cellX = (int)(x); + int cellY = (int)(y); + for (int gridY = cellY; gridY <= cellY + 1; ++gridY) + for (int gridX = cellX; gridX <= cellX + 1; ++gridX) { + int hash = perm[(perm[gridX & mask] + gridY) & mask]; + result += surflet(x - gridX, y - gridY, + gradsX[hash], gradsY[hash]); + } + return (result + 1) / 2; + } + + int rand(int i) { + int x = (i * i) % 256; + int y = (i + i * x) % 256; + return (int) (Integer.MAX_VALUE * SeedRandomGenerator.random(x, y, seed)); + } + } + +} \ No newline at end of file diff --git a/generated_world/src/main/java/mc/world/generated_world/SeedRandomGenerator.java b/generated_world/src/main/java/mc/world/generated_world/SeedRandomGenerator.java index a466791..b4af1ab 100644 --- a/generated_world/src/main/java/mc/world/generated_world/SeedRandomGenerator.java +++ b/generated_world/src/main/java/mc/world/generated_world/SeedRandomGenerator.java @@ -5,7 +5,7 @@ public final class SeedRandomGenerator { public static double random (int x, int y, int seed) { x = Math.abs(x - y) + 1; y = Math.abs(y - x) + 1; - for (int i = 0; i < 40; i ++) { + for (int i = 0; i < 20; i ++) { int a1 = x % 13; int a2 = x % 31; int a3 = x % 89; diff --git a/generated_world/src/main/java/mc/world/generated_world/WorldConstants.java b/generated_world/src/main/java/mc/world/generated_world/WorldConstants.java new file mode 100644 index 0000000..e82acb0 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/WorldConstants.java @@ -0,0 +1,17 @@ +package mc.world.generated_world; + +public final class WorldConstants { + + public static final String CHUNK_FILE_NAME_TEMPLATE = "chunk_{0}_{1}_{2}.dat"; + public static final String BIOME_FILE_NAME_TEMPLATE = "biome_{0}_{1}.dat"; + public static final String REGION_FILE_NAME_TEMPLATE = "r.{0}.{1}"; + + public static final int WORLD_MIN_HEIGHT = 28; + public static final int WORLD_SEA_LEVEL = 64; + public static final int WORLD_MAX_HEIGHT = 128; + + public static final int LANDFILL_GRASS_SURFACE_THIN = 5; + public static final double WORLD_ROUGHNRESS = 0.35; + + private WorldConstants () {} +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java new file mode 100644 index 0000000..e1c1d47 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ChunkProxy.java @@ -0,0 +1,128 @@ +package mc.world.generated_world.chunk; + +import mc.core.block.Block; +import mc.core.world.Biome; +import mc.core.world.Chunk; + +public class ChunkProxy implements Chunk { + private final Chunk chunk; + private volatile long lastUsage = System.currentTimeMillis(); + + public ChunkProxy(Chunk chunk) { + this.chunk = chunk; + } + + public long getLastUsage() { + synchronized (chunk) { + return lastUsage; + } + } + + private void use () { + synchronized (chunk) { + lastUsage = System.currentTimeMillis(); + } + } + + @Override + public int getBlockType(int x, int y, int z) { + use(); + return chunk.getBlockType(x, y, z); + } + + @Override + public void setBlockType(int x, int y, int z, int type) { + use(); + chunk.setBlockType(x, y, z, type); + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + use(); + return chunk.getBlockMetadata(x, y, z); + } + + @Override + public void setBlockMetadata(int x, int y, int z, int metadata) { + use(); + chunk.setBlockMetadata(x, y, z, metadata); + } + + @Override + public int getBlockLight(int x, int y, int z) { + use(); + return chunk.getBlockLight(x, y, z); + } + + @Override + public void setBlockLight(int x, int y, int z, int lightLevel) { + use(); + chunk.setBlockLight(x, y, z, lightLevel); + } + + @Override + public int getSkyLight(int x, int y, int z) { + use(); + return chunk.getSkyLight(x, y, z); + } + + @Override + public void setSkyLight(int x, int y, int z, int lightLevel) { + use(); + chunk.setSkyLight(x, y, z, lightLevel); + } + + @Override + public int getAddition(int x, int y, int z) { + use(); + return chunk.getAddition(x, y, z); + } + + @Override + public void setAddition(int x, int y, int z, int value) { + use(); + chunk.setAddition(x, y, z, value); + } + + @Override + public Biome getBiome(int x, int z) { + use(); + return chunk.getBiome(x, z); + } + + @Override + public void setBiome(int x, int z, Biome biome) { + use(); + chunk.setBiome(x, z, biome); + } + + @Override + public int getX() { + use(); + return chunk.getX(); + } + + @Override + public int getY() { + use(); + return chunk.getY(); + } + + @Override + public int getZ() { + use(); + return chunk.getZ(); + } + + @Override + public Block[] getNotAirBlocks() { + use(); + return chunk.getNotAirBlocks(); + } + + @Override + public void setBlock(int x, int y, int z, Block block) { + use(); + chunk.setBlock(x, y, z, block); + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/chunk/ProxiedChunkLoader.java b/generated_world/src/main/java/mc/world/generated_world/chunk/ProxiedChunkLoader.java new file mode 100644 index 0000000..f5aa630 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/chunk/ProxiedChunkLoader.java @@ -0,0 +1,9 @@ +package mc.world.generated_world.chunk; + +import mc.core.world.Chunk; +import mc.core.world.ChunkLoader; + +public interface ProxiedChunkLoader extends ChunkLoader { + @Override + Chunk loadOrGenerateChunk(int x, int y, int z); +} diff --git a/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java new file mode 100644 index 0000000..7c62c63 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/region/RegionImpl.java @@ -0,0 +1,63 @@ +package mc.world.generated_world.region; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import mc.core.world.*; +import mc.world.generated_world.chunk.ChunkProxy; +import org.springframework.beans.factory.annotation.Autowired; + +import java.text.MessageFormat; + +@Slf4j +@RequiredArgsConstructor +public class RegionImpl implements Region{ + @Getter + private final int x; + @Getter + private final int z; + private final ChunkProxy[][][] chunks = new ChunkProxy[16][16][16]; + private final Biome[][] biomes = new Biome[16][16]; + @Getter@Setter + private transient World world; + @Autowired + private ChunkLoader chunkLoader; + + @Override + public Chunk getChunkAt(int x, int y, int z) { + if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { + throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1} {2}]", x, y, z)); + } + Chunk chunk = chunks[x][y][z]; + if (chunk == null) { + chunk = chunkLoader.loadOrGenerateChunk(x, y, z); + chunks[x][y][z] = new ChunkProxy(chunk); + } + return chunk; + } + + @Override + public void setChunk(int x, int y, int z, Chunk chunk) { + if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= 16 || z >= 16) { + throw new RuntimeException(MessageFormat.format("Invalid chunk coordinates [{0} {1} {2}]", x, y, z)); + } + chunks[x][y][z] = new ChunkProxy(chunk); + } + + @Override + public Biome getBiomeAt(int x, int z) { + if (x < 0 || z < 0 || x >= 16 || z >= 16) { + throw new RuntimeException(MessageFormat.format("Invalid biome coordinates [{0} {1}]", x, z)); + } + return biomes[x][z]; + } + + @Override + public void setBiome(int x, int z, Biome biome) { + if (x < 0 || z < 0 || x >= 16 || z >= 16) { + throw new RuntimeException(MessageFormat.format("Invalid biome coordinates [{0} {1}]", x, z)); + } + biomes[x][z] = biome; + } +} diff --git a/generated_world/src/main/java/mc/world/generated_world/word/Temperature.java b/generated_world/src/main/java/mc/world/generated_world/word/Temperature.java new file mode 100644 index 0000000..1c2cb80 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/word/Temperature.java @@ -0,0 +1,9 @@ +package mc.world.generated_world.word; + +public enum Temperature { + FROST, + COLD, + WARM, + HOT, + HOTTEST +} diff --git a/generated_world/src/main/java/mc/world/generated_world/word/Wetness.java b/generated_world/src/main/java/mc/world/generated_world/word/Wetness.java new file mode 100644 index 0000000..b0c7e49 --- /dev/null +++ b/generated_world/src/main/java/mc/world/generated_world/word/Wetness.java @@ -0,0 +1,9 @@ +package mc.world.generated_world.word; + +public enum Wetness { + DRYEST, + DRY, + WET, + WETTER, + WATER +} diff --git a/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java b/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java index 795654a..4b4b2a7 100644 --- a/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java +++ b/generated_world/src/test/java/mc/world/generated_world/SeedRandomGeneratorTest.java @@ -9,6 +9,38 @@ import java.io.File; import static org.junit.Assert.*; public class SeedRandomGeneratorTest { + + @Test + public void randomGenSpeed () { + SeedRandomGenerator.random(0, 0, 0); + long avg = 0; + long min = -1; + long max = 0; + for (int i = 0; i < 500; i ++) { + int x = (int) (Math.random() * 10000); + int y = (int) (Math.random() * 10000); + int seed = (int) (Math.random() * 10000); + long time = System.nanoTime(); + SeedRandomGenerator.random(x, y, seed); + time = System.nanoTime() - time; + System.out.printf("[%s] \t%.3fms\n", i+1, time/1000d); + avg += time; + if (min == -1) { + min = time; + } else if (min > time) { + min = time; + } + if (max < time) { + max = time; + } + } + System.out.println(); + System.out.printf("Average time: %.3fms\n", avg/500000d); + System.out.printf("Minimum time: %.3fms\n", min/1000d); + System.out.printf("Maximum time: %.3fms\n", max/1000d); + assertTrue(avg/500 < 5000); + } + @Test public void randomTest() throws Exception { double maxDiff = 0; @@ -33,10 +65,12 @@ public class SeedRandomGeneratorTest { maxDisp = disp; } System.out.printf("Iteration %d.\t mid: %.3f, \tdisp %.6f\n", i + 1, mid, disp); + assertTrue(Math.abs(mid - 0.5) < 0.15); } System.out.printf("Max diff: %.3f\n", maxDiff); System.out.printf("Max disp: %.6f\n", maxDisp); + assertTrue(maxDiff > 0); }