Created
October 9, 2016 15:23
-
-
Save lkolbly/3ff2fc2e2f8ab1c9cc41b4da53ab0209 to your computer and use it in GitHub Desktop.
A sample class to generate Minecraft (Bukkit) terrains, using heightmap-based procedural generation techniques.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.rscheme.pillow.island; | |
import java.util.Random; | |
import java.util.List; | |
import java.util.Arrays; | |
import java.lang.Math; | |
import com.flowpowered.noise.Noise; | |
import com.flowpowered.noise.module.Module; | |
import org.bukkit.generator.ChunkGenerator; | |
import org.bukkit.generator.BlockPopulator; | |
import org.bukkit.World; | |
import org.bukkit.Material; | |
// Some different noise modules we can play with | |
import com.flowpowered.noise.module.source.Billow; | |
import com.flowpowered.noise.module.source.Perlin; | |
import com.flowpowered.noise.module.source.RidgedMulti; | |
import com.flowpowered.noise.module.source.Voronoi; | |
class Biome { | |
public int average; | |
public int variation; | |
public int period; | |
public int blockid; | |
public Module noiseSource; | |
public double getHeight(int x, int z) { | |
double val = noiseSource.getValue((double)x / (double)period, 0, (double)z / (double)period); | |
// This is where the height transfer function lives. | |
// The logistic function - for mesas | |
//return average + variation / (1.0 + Math.exp(10.0*val)); | |
// The linear function - a simple default | |
return variation * val + average; | |
} | |
} | |
public class GenerateNothing extends ChunkGenerator { | |
public static final int BIOME_HILLS = 0; | |
public static final int BIOME_PLAINS = 1; | |
public static final int BIOME_RIVER = 2; | |
//This needs to be set to return true to override minecraft's default behaviour | |
@Override | |
public boolean canSpawn(World world, int x, int z) { | |
return true; | |
} | |
//This converts relative chunk locations to bytes that can be written to the chunk | |
public int xyzToByte(int x, int y, int z) { | |
return (x * 16 + z) * 128 + y; | |
} | |
private int getBiome(int blockx, int blockz) { | |
// The noise function that decides the shape of biomes | |
//Voronoi v = new Voronoi(); | |
Perlin v = new Perlin(); | |
//RidgedMulti v = new RidgedMulti(); | |
v.setSeed(1234); | |
// If we want to have a distinct noise function for picking river biomes | |
//RidgedMulti rm = new RidgedMulti(); | |
//rm.setSeed(1235); | |
double D = 256; | |
double x = (double)blockx/D; | |
double z = (double)blockz/D; | |
/*if (rm.getValue(x,0,z) > 0.9) { | |
return BIOME_RIVER; | |
}*/ | |
double val = v.getValue(x,0,z); | |
if (val < 0.0) { | |
return BIOME_HILLS; | |
} else { | |
return BIOME_PLAINS; | |
} | |
} | |
private Biome getBiomeById(int id) { | |
if (id == BIOME_HILLS) { | |
// The noise function to decide the shape of the land within the biome | |
Perlin p = new Perlin(); | |
//Voronoi p = new Voronoi(); | |
//Billow p = new Billow(); | |
p.setSeed(1234); | |
return new Biome() { | |
{ | |
average = 80; | |
variation = 20; | |
period = 48; | |
blockid = Material.STONE.getId(); | |
noiseSource = p; | |
} | |
}; | |
} else if (id == BIOME_RIVER) { | |
Perlin p = new Perlin(); | |
p.setSeed(1234); | |
return new Biome() { | |
{ | |
average = 64; | |
variation = 0; | |
period = 48; | |
blockid = Material.WATER.getId(); | |
noiseSource = p; | |
} | |
}; | |
} else { // BIOME_PLAINS | |
Perlin p = new Perlin(); | |
p.setSeed(1235); | |
return new Biome() { | |
{ | |
average = 68; | |
variation = 5; | |
period = 128; | |
blockid = Material.GRASS.getId(); | |
noiseSource = p; | |
} | |
}; | |
} | |
} | |
// Generate column height before any smoothing or other operations. | |
private double generateHeightRaw(int x, int z) { | |
// Count the occurrences of each (of the two) biomes | |
int hills_count = 0; | |
int plains_count = 0; | |
int rivers_count = 0; | |
for (int dx=-7; dx<7; dx++) { | |
for (int dz=-7; dz<7; dz++) { | |
int biome = getBiome(x+dx,z+dz); | |
if (biome == 0) { | |
hills_count++; | |
} else if (biome == BIOME_RIVER) { | |
rivers_count++; | |
} else { | |
plains_count++; | |
} | |
} | |
} | |
// Get the height for each of the two biomes and average them | |
Biome hills = getBiomeById(0); | |
Biome plains = getBiomeById(1); | |
Biome river = getBiomeById(2); | |
double height = hills.getHeight(x,z)*hills_count + plains.getHeight(x,z)*plains_count + river.getHeight(x,z)*rivers_count; | |
height = height / (double)(hills_count + plains_count + rivers_count); | |
return height; | |
} | |
@Override | |
public byte[] generate(World world, Random rand, int chunkx, int chunkz) { | |
//Billow p = new Billow(); | |
//RidgedMulti p = new RidgedMulti(); | |
Perlin p = new Perlin(); | |
p.setSeed(1234); | |
byte[] result = new byte[32768]; | |
int y = 0; | |
//This will set the floor of each chunk at bedrock level to bedrock | |
for(int x=0; x<16; x++){ | |
for(int z=0; z<16; z++) { | |
result[xyzToByte(x,y,z)] = (byte) Material.BEDROCK.getId(); | |
} | |
} | |
double[] thresholds = new double[256]; | |
for (y=0; y<255; y++) { | |
thresholds[y] = 4.0 / (1.0 + Math.exp(-0.1*(-y + 68.0))) - 2; | |
} | |
// The cache saves so many noise function calls: | |
// - 4*2 for the smoothing function | |
// - 225 for computing the average biome | |
// This is *16*16, because that's per block. | |
double[] raw_heights = new double[18*18]; | |
for (int relx=-1; relx<17; relx++) { | |
for (int relz=-1; relz<17; relz++) { | |
int x = relx + chunkx*16; | |
int z = relz + chunkz*16; | |
raw_heights[(relx+1)*18+relz+1] = generateHeightRaw(x,z); | |
} | |
} | |
// Generate a heightmap | |
for (int relx=0; relx<16; relx++) { | |
for (int relz=0; relz<16; relz++) { | |
int x = relx + chunkx*16; | |
int z = relz + chunkz*16; | |
//double height = generateHeightRaw(x,z); | |
double height = (raw_heights[(relx+2)*18 + relz+1] + | |
raw_heights[(relx) *18 + relz+1] + | |
raw_heights[(relx+1)*18 + relz+2] + | |
raw_heights[(relx+1)*18 + relz ]) / 4.0; | |
int bid = getBiome(x, z); | |
Biome b = getBiomeById(bid); | |
for (y=1; y<128; y++) { | |
if (y < height) { | |
if (bid == BIOME_RIVER) { | |
// Special case for river biomes | |
if (height > 65) { | |
result[xyzToByte(relx,y,relz)] = (byte) Material.SAND.getId(); | |
} else { | |
result[xyzToByte(relx,y,relz)] = (byte) b.blockid; | |
} | |
} else { | |
result[xyzToByte(relx,y,relz)] = (byte) b.blockid; | |
} | |
} | |
} | |
} | |
} | |
return result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment