Skip to content

Instantly share code, notes, and snippets.

@Warlander
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Warlander/0a786007e6d1e68355b6 to your computer and use it in GitHub Desktop.
Save Warlander/0a786007e6d1e68355b6 to your computer and use it in GitHub Desktop.
Fractal terrain generator
package pl.warlander.terraingen.util;
import java.util.Random;
public class FastRandom extends Random {
private long seed;
public FastRandom(long seed) {
this.seed = seed;
}
public FastRandom() {
this.seed = getSeed();
}
protected static long getSeed() {
return System.currentTimeMillis() + System.nanoTime();
}
public void setSeed(long seed) {
this.seed = seed;
}
public long nextLong() {
seed ^= (seed << 21);
seed ^= (seed >>> 35);
seed ^= (seed << 4);
return seed;
}
public int nextInt() {
return (int) nextLong();
}
public long nextAbsLong() {
return Math.abs(nextLong());
}
public int nextAbsInt() {
return Math.abs((int) nextLong());
}
public long nextLong(long max) {
return nextAbsLong() % max;
}
public int nextInt(int max) {
return nextAbsInt() % max;
}
public long nextLong(long min, long max) {
return nextLong(max - min) + min;
}
public int nextInt(int min, int max) {
return nextInt(max - min) + min;
}
public double nextDouble() {
return nextLong() / (Long.MAX_VALUE - 1d);
}
public float nextFloat() {
return nextLong() / (Long.MAX_VALUE - 1f);
}
public double nextAbsDouble() {
return (nextDouble() + 1.0) / 2.0;
}
public float nextAbsFloat() {
return (nextFloat() + 1.0f) / 2.0f;
}
public double nextDouble(double min, double max) {
return nextAbsDouble() * (max - min) + min;
}
public float nextFloat(float min, float max) {
return nextAbsFloat() * (max - min) + min;
}
public boolean nextBoolean() {
return nextLong() > 0;
}
}
package pl.warlander.terraingen.core;
import pl.warlander.terraingen.util.FastRandom;
public class FractalLayer {
private final double value;
private final int size;
private final int width;
private final int height;
private final long seed;
private double[][] noise;
private boolean initialized;
private double smoothPower = 0;
FractalLayer(double value, int size, int width, int height, long seed) {
this.value = value;
this.size = size;
this.width = width;
this.height = height;
this.seed = seed;
this.initialized = false;
}
public FractalLayer setSmooth(double power) {
if (power<0 || power>=1) {
throw new IllegalArgumentException("Smooth power cannot be smaller than 0 or equal or higher than 1");
}
this.smoothPower = power;
return this;
}
void smooth(double power) {
double[][] newNoise = new double[width+1][height+1];
double centerValue = 1-power;
double nearValue = power/9;
for (int i = 1; i < width; i++) {
for (int i2 = 1; i2 < height; i2++) {
newNoise[i][i2] = smoothValue(i, i2, centerValue, nearValue);
}
}
for (int i=1; i<width; i++) {
newNoise[i][0] = smoothValueNorth(i, 0, centerValue, nearValue);
}
for (int i=1; i<width; i++) {
newNoise[i][height] = smoothValueSouth(i, height, centerValue, nearValue);
}
for (int i=1; i<height; i++) {
newNoise[0][i] = smoothValueWest(0, i, centerValue, nearValue);
}
for (int i=1; i<height; i++) {
newNoise[width][i] = smoothValueEast(width, i, centerValue, nearValue);
}
newNoise[width][height] = smoothValueSouthEast(width, height, centerValue, nearValue);
newNoise[width][0] = smoothValueNorthEast(width, 0, centerValue, nearValue);
newNoise[0][height] = smoothValueSouthWest(0, height, centerValue, nearValue);
newNoise[0][0] = smoothValueNorthWest(0, 0, centerValue, nearValue);
noise = newNoise;
}
private double smoothValue(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y-1] * nearValue;
val += noise[x-1][y] * nearValue;
val += noise[x-1][y+1] * nearValue;
val += noise[x][y-1] * nearValue;
val += noise[x][y] * centerValue;
val += noise[x][y+1] * nearValue;
val += noise[x+1][y-1] * nearValue;
val += noise[x+1][y] * nearValue;
val += noise[x+1][y+1] * nearValue;
return val;
}
private double smoothValueWest(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x][y-1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue);
val += noise[x][y+1] * nearValue * 2;
val += noise[x+1][y-1] * nearValue;
val += noise[x+1][y] * nearValue;
val += noise[x+1][y+1] * nearValue;
return val;
}
private double smoothValueEast(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y-1] * nearValue;
val += noise[x-1][y] * nearValue;
val += noise[x-1][y+1] * nearValue;
val += noise[x][y-1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue);
val += noise[x][y+1] * nearValue * 2;
return val;
}
private double smoothValueNorth(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y] * nearValue * 2;
val += noise[x-1][y+1] * nearValue;
val += noise[x][y] * (centerValue + nearValue);
val += noise[x][y+1] * nearValue;
val += noise[x+1][y] * nearValue * 2;
val += noise[x+1][y+1] * nearValue;
return val;
}
private double smoothValueSouth(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y-1] * nearValue;
val += noise[x-1][y] * nearValue * 2;
val += noise[x][y-1] * nearValue;
val += noise[x][y] * (centerValue + nearValue);
val += noise[x+1][y-1] * nearValue;
val += noise[x+1][y] * nearValue * 2;
return val;
}
private double smoothValueNorthWest(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x+1][y+1] * nearValue;
val += noise[x+1][y] * nearValue * 2;
val += noise[x][y+1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue * 3);
return val;
}
private double smoothValueSouthWest(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x+1][y-1] * nearValue;
val += noise[x+1][y] * nearValue * 2;
val += noise[x][y-1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue * 3);
return val;
}
private double smoothValueNorthEast(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y+1] * nearValue;
val += noise[x-1][y] * nearValue * 2;
val += noise[x][y+1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue * 3);
return val;
}
private double smoothValueSouthEast(int x, int y, double centerValue, double nearValue) {
double val = 0;
val += noise[x-1][y-1] * nearValue;
val += noise[x-1][y] * nearValue * 2;
val += noise[x][y-1] * nearValue * 2;
val += noise[x][y] * (centerValue + nearValue * 3);
return val;
}
void initialize() {
if (initialized) {
return;
}
this.noise = new double[width+1][height+1];
FastRandom rand = new FastRandom(seed);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
noise[x][y] = (rand.nextDouble(-1, 1)) * value;
}
}
if (smoothPower>0) {
smooth(smoothPower);
}
initialized = true;
}
double get(int x, int y) {
if (x<0 || x>=width*size || y<0 || y>=height*size) {
throw new IllegalArgumentException("Width or height is too small or too high");
}
final int startX = x/size;
final int startY = y/size;
final double h00 = noise[startX][startY];
final double h10 = noise[startX+1][startY];
final double h01 = noise[startX][startY+1];
final double h11 = noise[startX+1][startY+1];
final double ratioX = (double)x/size-startX;
final double ratioY = (double)y/size-startY;
final double revRatioX = 1 - ratioX;
final double revRatioY = 1 - ratioY;
final double x0 = (h00*revRatioX + h10*ratioX);
final double x1 = (h01*revRatioX + h11*ratioX);
return (x0*revRatioY + x1*ratioY);
}
double getValue() {
return value;
}
public String toString() {
return "Fractal Layer size: "+size+", value: "+value;
}
}
package pl.warlander.terraingen.core;
import java.util.ArrayList;
import java.util.Random;
public class HeightGenerator {
private final long seed;
private final int width;
private final int height;
private final Random rand;
private final ArrayList<FractalLayer> genLayers;
public HeightGenerator(final long seed, final int width, final int height) {
this.seed = seed;
this.width = width;
this.height = height;
this.rand = new Random(seed);
genLayers = new ArrayList<>();
}
public FractalLayer addFractalLayer(final double value, final int size) {
if (width%size!=0 || height%size!=0) {
throw new IllegalArgumentException("Size must be a divisor of width and height");
}
FractalLayer layer = new FractalLayer(value, size, width/size, height/size, rand.nextLong());
genLayers.add(layer);
return layer;
}
public double[][] genArray() {
double valuesSum = 0;
for (FractalLayer layer : genLayers) {
valuesSum += layer.getValue();
}
final double[][] result = new double[width][height];
genLayers.parallelStream().forEach(layer -> {
layer.initialize();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
result[x][y] += layer.get(x, y);
}
}
});
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
result[x][y] /= valuesSum;
}
}
return result;
}
public long getSeed() {
return seed;
}
}
package pl.warlander.terraingen.util;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class ImageExporter {
public static BufferedImage exportToImage(double[][] terrain) {
int width = terrain.length;
int height = terrain[0].length;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final int[] pix = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
for (int i = 0; i < width; i++) {
for (int i2 = 0; i2 < height; i2++) {
float value = (float) terrain[i][i2];
boolean water = value < 0;
value = Math.abs(value);
value = Math.min(value, 1);
int c = (int) (255 - value * 255);
if (water) {
pix[i + i2 * width] = 0xFF000000 | c;
}
else {
pix[i + i2 * width] = 0xFF000000 | (c << 8);
}
}
}
return img;
}
}
package pl.warlander.terraingen;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import pl.warlander.terraingen.core.HeightGenerator;
import pl.warlander.terraingen.util.ImageExporter;
public class Launch {
public static void main(String[] args) {
HeightGenerator gen = new HeightGenerator(12346, 2048, 2048);
gen.addFractalLayer(1d, 512);
//generateAndSaveImage(gen, "1.png");
gen.addFractalLayer(1d/2, 128).setSmooth(0.2);
//generateAndSaveImage(gen, "2.png");
gen.addFractalLayer(1d/4, 64).setSmooth(0.3);
//generateAndSaveImage(gen, "3.png");
gen.addFractalLayer(1d/8, 32).setSmooth(0.3);
//generateAndSaveImage(gen, "4.png");
gen.addFractalLayer(1d/16, 16).setSmooth(0.3);
//generateAndSaveImage(gen, "5.png");
gen.addFractalLayer(1d/32, 8).setSmooth(0.3);
//generateAndSaveImage(gen, "6.png");
gen.addFractalLayer(1d/64, 4).setSmooth(0.2);
//generateAndSaveImage(gen, "7.png");
gen.addFractalLayer(1d/128, 2);
//generateAndSaveImage(gen, "8.png");
gen.addFractalLayer(1d/256, 1);
//gen.genArray();
generateAndSaveImage(gen, "9.png");
}
private static void generateAndSaveImage(HeightGenerator gen, String file) {
try {
ImageIO.write(ImageExporter.exportToImage(gen.genArray()), "png", new File(file));
} catch (IOException ex) {
Logger.getLogger(Launch.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment