Last active
August 29, 2015 14:10
-
-
Save Warlander/0a786007e6d1e68355b6 to your computer and use it in GitHub Desktop.
Fractal terrain generator
This file contains hidden or 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 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; | |
} | |
} |
This file contains hidden or 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 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; | |
} | |
} |
This file contains hidden or 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 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; | |
} | |
} |
This file contains hidden or 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 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; | |
} | |
} |
This file contains hidden or 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 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