Skip to content

Instantly share code, notes, and snippets.

@lkolbly
Created October 9, 2016 15:23
Show Gist options
  • Save lkolbly/3ff2fc2e2f8ab1c9cc41b4da53ab0209 to your computer and use it in GitHub Desktop.
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.
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