package net.glowstone.generator; import net.glowstone.GlowWorld; import net.glowstone.constants.GlowBiome; import net.glowstone.generator.ground.*; import net.glowstone.generator.ground.MesaGroundGenerator.MesaType; import net.glowstone.generator.populators.OverworldPopulator; import net.glowstone.generator.populators.StructurePopulator; import net.glowstone.generator.populators.overworld.SnowPopulator; import net.glowstone.util.noise.PerlinOctaveGenerator; import net.glowstone.util.noise.SimplexOctaveGenerator; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.util.noise.OctaveGenerator; import java.util.HashMap; import java.util.Map; import java.util.Random; import static org.bukkit.block.Biome.*; public class OverworldGenerator extends GlowChunkGenerator { // Still need to parse ServerConfig.Key.GENERATOR_SETTINGS string // and set below fields from that. private static final double COORDINATE_SCALE = 684.412D; // coordinateScale private static final double HEIGHT_SCALE = 684.412D; // heightScale private static final double HEIGHT_NOISE_SCALE_X = 200.0D; // depthNoiseScaleX private static final double HEIGHT_NOISE_SCALE_Z = 200.0D; // depthNoiseScaleZ private static final double DETAIL_NOISE_SCALE_X = 80.0D; // mainNoiseScaleX private static final double DETAIL_NOISE_SCALE_Y = 160.0D; // mainNoiseScaleY private static final double DETAIL_NOISE_SCALE_Z = 80.0D; // mainNoiseScaleZ private static final double SURFACE_SCALE = 1 / 16.0D; private static final double BASE_SIZE = 8.5D; // baseSize private static final double STRETCH_Y = 12.0D; // stretchY private static final double BIOME_HEIGHT_OFFSET = 0.0D; // biomeDepthOffset private static final double BIOME_HEIGHT_WEIGHT = 1.0D; // biomeDepthWeight private static final double BIOME_SCALE_OFFSET = 0.0D; // biomeScaleOffset private static final double BIOME_SCALE_WEIGHT = 1.0D; // biomeScaleWeight private static final double[][] ELEVATION_WEIGHT = new double[5][5]; private static final Map<Biome, GroundGenerator> GROUND_MAP = new HashMap<>(); private static final Map<Biome, BiomeHeight> HEIGHT_MAP = new HashMap<>(); private final double[][][] density = new double[5][5][33]; private final GroundGenerator groundGen = new GroundGenerator(); private final BiomeHeight defaultHeight = BiomeHeight.DEFAULT; public OverworldGenerator() { super(new OverworldPopulator(), new StructurePopulator(), new SnowPopulator()); } @Override public short[][] generateExtBlockSectionsWithData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomes) { final short[][] buf = generateRawTerrain(world, chunkX, chunkZ); int cx = chunkX << 4; int cz = chunkZ << 4; final double[] surfaceNoise = ((SimplexOctaveGenerator) getWorldOctaves(world).get("surface")).fBm(cx, cz, 0.5D, 0.5D); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { if (GROUND_MAP.containsKey(biomes.getBiome(x, z))) { GROUND_MAP.get(biomes.getBiome(x, z)).generateTerrainColumn(buf, world, random, cx + x, cz + z, biomes.getBiome(x, z), surfaceNoise[x | (z << 4)]); } else { groundGen.generateTerrainColumn(buf, world, random, cx + x, cz + z, biomes.getBiome(x, z), surfaceNoise[x | (z << 4)]); } } } return buf; } @Override public boolean canSpawn(World world, int x, int z) { final Block block = world.getHighestBlockAt(x, z).getRelative(BlockFace.DOWN); return block.getType() == Material.GRASS; } @Override protected void createWorldOctaves(World world, Map<String, OctaveGenerator> octaves) { final Random seed = new Random(world.getSeed()); OctaveGenerator gen = new PerlinOctaveGenerator(seed, 16, 5, 5); gen.setXScale(HEIGHT_NOISE_SCALE_X); gen.setZScale(HEIGHT_NOISE_SCALE_Z); octaves.put("height", gen); gen = new PerlinOctaveGenerator(seed, 16, 5, 33, 5); gen.setXScale(COORDINATE_SCALE); gen.setYScale(HEIGHT_SCALE); gen.setZScale(COORDINATE_SCALE); octaves.put("roughness", gen); gen = new PerlinOctaveGenerator(seed, 16, 5, 33, 5); gen.setXScale(COORDINATE_SCALE); gen.setYScale(HEIGHT_SCALE); gen.setZScale(COORDINATE_SCALE); octaves.put("roughness2", gen); gen = new PerlinOctaveGenerator(seed, 8, 5, 33, 5); gen.setXScale(COORDINATE_SCALE / DETAIL_NOISE_SCALE_X); gen.setYScale(HEIGHT_SCALE / DETAIL_NOISE_SCALE_Y); gen.setZScale(COORDINATE_SCALE / DETAIL_NOISE_SCALE_Z); octaves.put("detail", gen); gen = new SimplexOctaveGenerator(seed, 4, 16, 16); gen.setScale(SURFACE_SCALE); octaves.put("surface", gen); } @SuppressWarnings("deprecation") private void set(short[][] buf, int x, int y, int z, Material id) { if (buf[y >> 4] == null) { buf[y >> 4] = new short[4096]; } buf[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = (short) (id.getId() << 4); } private short[][] generateRawTerrain(World world, int chunkX, int chunkZ) { generateTerrainDensity(world, chunkX, chunkZ); int seaLevel = world.getSeaLevel(); final short[][] buf = new short[16][]; // Terrain densities where sampled at a lower res (scaled 4x along vertical, 8x along horizontal) // so it's needed to re-scale it. Linear interpolation is used to fill in the gaps. for (int i = 0; i < 5 - 1; i++) { for (int j = 0; j < 5 - 1; j++) { for (int k = 0; k < 33 - 1; k++) { // 2x2 grid double d1 = density[i][j][k]; double d2 = density[i + 1][j][k]; double d3 = density[i][j + 1][k]; double d4 = density[i + 1][j + 1][k]; // 2x2 grid (row above) double d5 = (density[i][j][k + 1] - d1) / 8; double d6 = (density[i + 1][j][k + 1] - d2) / 8; double d7 = (density[i][j + 1][k + 1] - d3) / 8; double d8 = (density[i + 1][j + 1][k + 1] - d4) / 8; for (int l = 0; l < 8; l++) { double d9 = d1; double d10 = d3; for (int m = 0; m < 4; m++) { double dens = d9; for (int n = 0; n < 4; n++) { // any density higher than 0 is ground, any density lower or equal to 0 is air // (or water if under the sea level). if (dens > 0) { set(buf, m + (i << 2), l + (k << 3), n + (j << 2), Material.STONE); } else if (l + (k << 3) < seaLevel - 1) { set(buf, m + (i << 2), l + (k << 3), n + (j << 2), Material.STATIONARY_WATER); } // interpolation along z dens += (d10 - d9) / 4; } // interpolation along x d9 += (d2 - d1) / 4; // interpolate along z d10 += (d4 - d3) / 4; } // interpolation along y d1 += d5; d3 += d7; d2 += d6; d4 += d8; } } } } return buf; } private void generateTerrainDensity(World world, int x, int z) { final WorldType type = world.getWorldType(); // Scaling chunk x and z coordinates (4x, see below) x <<= 2; z <<= 2; // Get biome grid data at lower res (scaled 4x, at this scale a chunk is 4x4 columns of the biome grid), // we are loosing biome detail but saving huge amount of computation. // We need 1 chunk (4 columns) + 1 column for later needed outer edges (1 column) and at least 2 columns // on each side to be able to cover every value. // 4 + 1 + 2 + 2 = 9 columns but the biomegrid generator needs a multiple of 2 so we ask 10 columns wide // to the biomegrid generator. // This gives a total of 81 biome grid columns to work with, and this includes the chunk neighborhood. final int[] biomeGrid = ((GlowWorld) world).getChunkManager().getBiomeGridAtLowerRes(x - 2, z - 2, 10, 10); final Map<String, OctaveGenerator> octaves = getWorldOctaves(world); final double[] heightNoise = ((PerlinOctaveGenerator) octaves.get("height")).fBm(x, z, 0.5D, 2.0D); final double[] roughnessNoise = ((PerlinOctaveGenerator) octaves.get("roughness")).fBm(x, 0, z, 0.5D, 2.0D); final double[] roughnessNoise2 = ((PerlinOctaveGenerator) octaves.get("roughness2")).fBm(x, 0, z, 0.5D, 2.0D); final double[] detailNoise = ((PerlinOctaveGenerator) octaves.get("detail")).fBm(x, 0, z, 0.5D, 2.0D); int index = 0; int indexHeight = 0; // Sampling densities. // Ideally we would sample 512 (4x4x32) values but in reality we need 825 values (5x5x33). // This is because linear interpolation is done later to re-scale so we need right and // bottom edge values if we want it to be "seamless". // You can check this picture to have a visualization of how the biomegrid is traversed (2D plan): // http://i.imgur.com/s4whlZE.png // The big square grid represents our lower res biomegrid columns, and the very small square grid // represents the normal biome grid columns (at block level) and the reason why it's required to // re-scale it and do linear interpolation before densities can be used to generate raw terrain. for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { double avgHeightScale = 0; double avgHeightBase = 0; double totalWeight = 0; final Biome biome = GlowBiome.getBiome(biomeGrid[i + 2 + (j + 2) * 10]); final BiomeHeight biomeHeight = HEIGHT_MAP.containsKey(biome) ? HEIGHT_MAP.get(biome) : defaultHeight; // Sampling an average height base and scale by visiting the neighborhood // of the current biomegrid column. for (int m = 0; m < 5; m++) { for (int n = 0; n < 5; n++) { final Biome nearBiome = GlowBiome.getBiome(biomeGrid[i + m + (j + n) * 10]); final BiomeHeight nearBiomeHeight = HEIGHT_MAP.containsKey(nearBiome) ? HEIGHT_MAP.get(nearBiome) : defaultHeight; double heightBase = BIOME_HEIGHT_OFFSET + nearBiomeHeight.getHeight() * BIOME_HEIGHT_WEIGHT; double heightScale = BIOME_SCALE_OFFSET + nearBiomeHeight.getScale() * BIOME_SCALE_WEIGHT; if (type == WorldType.AMPLIFIED && heightBase > 0) { heightBase = 1.0D + heightBase * 2.0D; heightScale = 1.0D + heightScale * 4.0D; } double weight = ELEVATION_WEIGHT[m][n] / (heightBase + 2.0D); if (nearBiomeHeight.getHeight() > biomeHeight.getHeight()) { weight *= 0.5D; } avgHeightScale += heightScale * weight; avgHeightBase += heightBase * weight; totalWeight += weight; } } avgHeightScale /= totalWeight; avgHeightBase /= totalWeight; avgHeightScale = avgHeightScale * 0.9D + 0.1D; avgHeightBase = (avgHeightBase * 4.0D - 1.0D) / 8.0D; double noiseH = heightNoise[indexHeight++] / 8000.0D; if (noiseH < 0) { noiseH = Math.abs(noiseH) * 0.3D; } noiseH = noiseH * 3.0D - 2.0D; if (noiseH < 0) { noiseH = Math.max(noiseH * 0.5D, -1) / 1.4D * 0.5D; } else { noiseH = Math.min(noiseH, 1) / 8.0D; } noiseH = ((noiseH * 0.2D + avgHeightBase) * BASE_SIZE / 8.0D) * 4.0D + BASE_SIZE; for (int k = 0; k < 33; k++) { // density should be lower and lower as we climb up, this gets a height value to // substract from the noise. double nH = (k - noiseH) * STRETCH_Y * 128.0D / 256.0D / avgHeightScale; if (nH < 0.0D) { nH *= 4.0D; } double noiseR = roughnessNoise[index] / 512.0D; double noiseR2 = roughnessNoise2[index] / 512.0D; double noiseD = (detailNoise[index] / 10.0D + 1.0D) / 2.0D; // linear interpolation double dens = noiseD < 0 ? noiseR : noiseD > 1 ? noiseR2 : noiseR + (noiseR2 - noiseR) * noiseD; dens -= nH; index++; if (k > 29) { double lowering = (k - 29) / 3.0D; // linear interpolation dens = dens * (1.0D - lowering) + (-10.0D * lowering); } density[i][j][k] = dens; } } } } private static void setBiomeSpecificGround(GroundGenerator gen, Biome... biomes) { for (Biome biome : biomes) { GROUND_MAP.put(biome, gen); } } private static void setBiomeHeight(BiomeHeight height, Biome... biomes) { for (Biome biome : biomes) { HEIGHT_MAP.put(biome, height); } } private static class BiomeHeight { public static final BiomeHeight DEFAULT = new BiomeHeight(0.1D, 0.2D); public static final BiomeHeight FLAT_SHORE = new BiomeHeight(0.0D, 0.025D); public static final BiomeHeight HIGH_PLATEAU = new BiomeHeight(1.5D, 0.025D); public static final BiomeHeight FLATLANDS = new BiomeHeight(0.125D, 0.05D); public static final BiomeHeight SWAMPLAND = new BiomeHeight(-0.2D, 0.1D); public static final BiomeHeight MID_PLAINS = new BiomeHeight(0.2D, 0.2D); public static final BiomeHeight FLATLANDS_HILLS = new BiomeHeight(0.275D, 0.25D); public static final BiomeHeight SWAMPLAND_HILLS = new BiomeHeight(-0.1D, 0.3D); public static final BiomeHeight LOW_HILLS = new BiomeHeight(0.2D, 0.3D); public static final BiomeHeight HILLS = new BiomeHeight(0.45D, 0.3D); public static final BiomeHeight MID_HILLS2 = new BiomeHeight(0.1D, 0.4D); public static final BiomeHeight DEFAULT_HILLS = new BiomeHeight(0.2D, 0.4D); public static final BiomeHeight MID_HILLS = new BiomeHeight(0.3D, 0.4D); public static final BiomeHeight BIG_HILLS = new BiomeHeight(0.525D, 0.55D); public static final BiomeHeight BIG_HILLS2 = new BiomeHeight(0.55D, 0.5D); public static final BiomeHeight EXTREME_HILLS = new BiomeHeight(1.0D, 0.5D); public static final BiomeHeight ROCKY_SHORE = new BiomeHeight(0.1D, 0.8D); public static final BiomeHeight LOW_SPIKES = new BiomeHeight(0.4125D, 1.325D); public static final BiomeHeight HIGH_SPIKES = new BiomeHeight(1.1D, 1.3125D); public static final BiomeHeight RIVER = new BiomeHeight(-0.5D, 0.0D); public static final BiomeHeight OCEAN = new BiomeHeight(-1.0D, 0.1D); public static final BiomeHeight DEEP_OCEAN = new BiomeHeight(-1.8D, 0.1D); private final double height; private final double scale; public BiomeHeight(double height, double scale) { this.height = height; this.scale = scale; } public double getHeight() { return height; } public double getScale() { return scale; } } static { setBiomeSpecificGround(new SandyGroundGenerator(), BEACH, COLD_BEACH, DESERT, DESERT_HILLS, DESERT_MOUNTAINS); setBiomeSpecificGround(new RockyGroundGenerator(), STONE_BEACH); setBiomeSpecificGround(new SnowyGroundGenerator(), ICE_PLAINS_SPIKES); setBiomeSpecificGround(new MycelGroundGenerator(), MUSHROOM_ISLAND, MUSHROOM_SHORE); setBiomeSpecificGround(new StonePatchGroundGenerator(), EXTREME_HILLS); setBiomeSpecificGround(new GravelPatchGroundGenerator(), EXTREME_HILLS_MOUNTAINS, EXTREME_HILLS_PLUS_MOUNTAINS); setBiomeSpecificGround(new DirtAndStonePatchGroundGenerator(), SAVANNA_MOUNTAINS, SAVANNA_PLATEAU_MOUNTAINS); setBiomeSpecificGround(new DirtPatchGroundGenerator(), MEGA_TAIGA, MEGA_TAIGA_HILLS, MEGA_SPRUCE_TAIGA, MEGA_SPRUCE_TAIGA_HILLS); setBiomeSpecificGround(new MesaGroundGenerator(), MESA, MESA_PLATEAU, MESA_PLATEAU_MOUNTAINS); setBiomeSpecificGround(new MesaGroundGenerator(MesaType.BRYCE), MESA_BRYCE); setBiomeSpecificGround(new MesaGroundGenerator(MesaType.FOREST), MESA_PLATEAU_FOREST, MESA_PLATEAU_FOREST_MOUNTAINS); setBiomeHeight(BiomeHeight.OCEAN, OCEAN, FROZEN_OCEAN); setBiomeHeight(BiomeHeight.DEEP_OCEAN, DEEP_OCEAN); setBiomeHeight(BiomeHeight.RIVER, RIVER, FROZEN_RIVER); setBiomeHeight(BiomeHeight.FLAT_SHORE, BEACH, COLD_BEACH, MUSHROOM_SHORE); setBiomeHeight(BiomeHeight.ROCKY_SHORE, STONE_BEACH); setBiomeHeight(BiomeHeight.FLATLANDS, DESERT, ICE_PLAINS, SAVANNA); setBiomeHeight(BiomeHeight.EXTREME_HILLS, EXTREME_HILLS, EXTREME_HILLS_PLUS, EXTREME_HILLS_MOUNTAINS, EXTREME_HILLS_PLUS_MOUNTAINS); setBiomeHeight(BiomeHeight.MID_PLAINS, TAIGA, COLD_TAIGA, MEGA_TAIGA); setBiomeHeight(BiomeHeight.SWAMPLAND, SWAMPLAND); setBiomeHeight(BiomeHeight.LOW_HILLS, MUSHROOM_ISLAND); setBiomeHeight(BiomeHeight.HILLS, ICE_MOUNTAINS, DESERT_HILLS, FOREST_HILLS, TAIGA_HILLS, SMALL_MOUNTAINS, JUNGLE_HILLS, BIRCH_FOREST_HILLS, COLD_TAIGA_HILLS, MEGA_TAIGA_HILLS, MESA_PLATEAU_FOREST_MOUNTAINS, MESA_PLATEAU_MOUNTAINS); setBiomeHeight(BiomeHeight.HIGH_PLATEAU, SAVANNA_PLATEAU, MESA_PLATEAU_FOREST, MESA_PLATEAU); setBiomeHeight(BiomeHeight.FLATLANDS_HILLS, DESERT_MOUNTAINS); setBiomeHeight(BiomeHeight.BIG_HILLS, ICE_PLAINS_SPIKES); setBiomeHeight(BiomeHeight.BIG_HILLS2, BIRCH_FOREST_HILLS_MOUNTAINS); setBiomeHeight(BiomeHeight.SWAMPLAND_HILLS, SWAMPLAND_MOUNTAINS); setBiomeHeight(BiomeHeight.DEFAULT_HILLS, JUNGLE_MOUNTAINS, JUNGLE_EDGE_MOUNTAINS, BIRCH_FOREST_MOUNTAINS, ROOFED_FOREST_MOUNTAINS); setBiomeHeight(BiomeHeight.MID_HILLS, TAIGA_MOUNTAINS, COLD_TAIGA_MOUNTAINS, MEGA_SPRUCE_TAIGA, MEGA_SPRUCE_TAIGA_HILLS); setBiomeHeight(BiomeHeight.MID_HILLS2, FLOWER_FOREST); setBiomeHeight(BiomeHeight.LOW_SPIKES, SAVANNA_MOUNTAINS); setBiomeHeight(BiomeHeight.HIGH_SPIKES, SAVANNA_PLATEAU_MOUNTAINS); // fill a 5x5 array with values that acts as elevation weight on chunk neighboring, // this can be viewed as a parabolic field: the center gets the more weight, and the // weight decreases as distance increases from the center. This is applied on the // lower scale biome grid. for (int x = 0; x < 5; x++) { for (int z = 0; z < 5; z++) { int sqX = x - 2; sqX *= sqX; int sqZ = z - 2; sqZ *= sqZ; ELEVATION_WEIGHT[x][z] = 10.0D / Math.sqrt(sqX + sqZ + 0.2D); } } } }