I looked all over the internet and researched Perlin noise, however, I am still confused.
I am using java and libgdx. I have got a Perlin class to work and generate noise but I'm not sure if the values its giving are correct. How do I check it actually is outputting Perlin noise?
If my implementation is correct I don't know where to go from there to make random terrain. How would I map Perlin noise to tiles? Currently I have 4 basic tiles; water, sand, rock, and grass.
package com.bracco.thrive.world;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
public class WorldGeneration {
Perlin noise = new Perlin();
private SpriteBatch spriteBatch;
//private boolean debug = false;
private TextureRegion[] regions = new TextureRegion[4];
private Texture texture;
float x = 110;
float y = 120;
float originX = 0;
float originY = 16;
float width = 16;
float height = 16;
float scaleX = 1;
float scaleY = 1;
float rotation = 1;
@SuppressWarnings("static-access")
public void createWorld(){
spriteBatch = new SpriteBatch();
texture = new Texture(Gdx.files.internal("assets/data/textures/basictextures.png"));
regions[0] = new TextureRegion(texture,0,0,16,16); //grass
regions[1] = new TextureRegion(texture,16,0,16,16); //water
regions[2] = new TextureRegion(texture,0,17,16,16); //sand
regions[3] = new TextureRegion(texture,17,17,16,16); //rock
float[][] seed = noise.GenerateWhiteNoise(50, 50);
for (int i = 0;i < seed.length; i++){
for ( int j = 0; j < seed[i].length; j++){
System.out.println(seed[i][j] + " ");
}
}
float[][] seedE = noise.GenerateSmoothNoise( seed, 6);
for (int i = 0;i < seedE.length; i++){
for ( int j = 0; j < seedE[i].length; j++){
System.out.println(seedE[i][j] + " ");
}
}
float[][] perlinNoise = noise.GeneratePerlinNoise(seedE, 8);
for (int i = 0;i < perlinNoise.length; i++){
for ( int j = 0; j < perlinNoise[i].length; j++){
System.out.println(perlinNoise[i][j] + " ");
}
}
}
public void render(){
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
spriteBatch.begin();
//spriteBatch.draw(texture, 0, 0, 16, 16);
for (int i = 0; i < regions.length; i++){
spriteBatch.draw(regions[i],75 * (i + 1),100);
}
spriteBatch.end();
}
}
package com.bracco.thrive.world;
import java.util.Random;
public class Perlin {
public static float[][] GenerateWhiteNoise(int width,int height){
Random random = new Random((long) (Math.round(Math.random() * 100 * Math.random() * 10))); //Seed to 0 for testing
float[][] noise = new float[width][height];
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++){
noise[i][j] = (float)(Math.random() % 1);
}
}
return noise;
}
float[][] GenerateSmoothNoise(float[][] baseNoise, int octave)
{
int width = baseNoise.length;
int height = baseNoise.length;
float[][] smoothNoise = new float[width][height];
int samplePeriod = 1 << octave; // calculates 2 ^ k
float sampleFrequency = 1.0f / samplePeriod;
for (int i = 0; i < width; i++)
{
//calculate the horizontal sampling indices
int sample_i0 = (i / samplePeriod) * samplePeriod;
int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
float horizontal_blend = (i - sample_i0) * sampleFrequency;
for (int j = 0; j < height; j++)
{
//calculate the vertical sampling indices
int sample_j0 = (j / samplePeriod) * samplePeriod;
int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
float vertical_blend = (j - sample_j0) * sampleFrequency;
//blend the top two corners
float top = Interpolate(baseNoise[sample_i0][sample_j0],
baseNoise[sample_i1][sample_j0], horizontal_blend);
//blend the bottom two corners
float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
baseNoise[sample_i1][sample_j1], horizontal_blend);
//final blend
smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
}
}
return smoothNoise;
}
float Interpolate(float x0, float x1, float alpha)
{
return x0 * (1 - alpha) + alpha * x1;
}
float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount)
{
int width = baseNoise.length;
int height = baseNoise[0].length;
float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing
float persistance = 0.5f;
//generate smooth noise
for (int i = 0; i < octaveCount; i++)
{
smoothNoise[i] = GenerateSmoothNoise(baseNoise, i);
}
float[][] perlinNoise = new float[width][height];
float amplitude = 1.0f;
float totalAmplitude = 0.0f;
//blend noise together
for (int octave = octaveCount - 1; octave >= 0; octave--)
{
amplitude *= persistance;
totalAmplitude += amplitude;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
}
}
}
//normalisation
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
perlinNoise[i][j] /= totalAmplitude;
}
}
return perlinNoise;
}
}
Correctness of perlin noise
Regarding if your perlin noise is 'correct'; the easiest way to see if your perlin noise (or technically fractal noise based upon several octaves of perlin noise) is working is to use the values of your perlin noise to generate a greyscale image, this image should look like some kind of landscape (rolling hills, or mountains depending on the parameters you chose for the persistance (and to a less extent the number of octaves). Some examples of perlin noise is:
Low Persisance:
or
High Persisance:
or
High Persisance (zoomed out):
These greyscale images are produced by the following code
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageWriter {
//just convinence methods for debug
public static void greyWriteImage(double[][] data){
//this takes and array of doubles between 0 and 1 and generates a grey scale image from them
BufferedImage image = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < data[0].length; y++)
{
for (int x = 0; x < data.length; x++)
{
if (data[x][y]>1){
data[x][y]=1;
}
if (data[x][y]<0){
data[x][y]=0;
}
Color col=new Color((float)data[x][y],(float)data[x][y],(float)data[x][y]);
image.setRGB(x, y, col.getRGB());
}
}
try {
// retrieve image
File outputfile = new File("saved.png");
outputfile.createNewFile();
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
//o no!
}
}
public static void main(String args[]){
double[][] data=new double[2][4];
data[0][0]=0.5;
data[0][5]=1;
data[1][0]=0.7;
data[1][6]=1;
greyWriteImage(data);
}
}
This code assumes each entry will be between 0 and 1, but perlin noise usually produces between -1 and 1, scale according to your implimentation. Assuming your perlin noise will give a value for any x,y then you can run this using the following code
//generates 100 by 100 data points within the specified range
double iStart=0;
double iEnd=500;
double jStart=0;
double jEnd=500;
double[][] result=new double[100][100];
for(int i=0;i<100;i++){
for(int j=0;j<100;j++){
int x=(int)(iStart+i*((iEnd-iStart)/100));
int y=(int)(jStart+j*((jEnd-jStart)/100));
result[i][j]=0.5*(1+perlinNoise.getNoise(x,y));
}
}
ImageWriter.greyWriteImage(result);
My implimentation expects integer x and y. Feel free to modify if this is not the case for you
Mapping to tiles
This is entirely up to you, you need to define certain ranges of the perlin noise value to create certain tiles. Be aware however that perlin noise is biased towards 0. Assuming 2D you could get nice results by taking the landscape analogy semi literally, low values=water, lowish values=sand, medium values=grass, high values =snow.
Also be aware that in some implementations (eg minecraft biomes and caverns) several random values are combined to create an overall result. See https://softwareengineering.stackexchange.com/questions/202992/randomization-of-biomes/203040#203040
Ideas for improvement
If you find that perlin noise generation is too slow then consider simplex noise, it has very similar properties but is more efficient (especially at higher dimentions). Simplex noise is however considerably more complex mathematically.