Using Perlin noise to generate a 2d tile map

user2489897 picture user2489897 · Jul 3, 2013 · Viewed 12.4k times · Source

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;
    }
}

Answer

Richard Tingle picture Richard Tingle · Jul 5, 2013

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:
Persisance of 0.5

or

High Persisance:
Persisance of 0.7

or

High Persisance (zoomed out):
enter image description here

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.