0

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;
    }
}
Richard Tingle
  • 16,906
  • 5
  • 52
  • 77
user2489897
  • 77
  • 1
  • 1
  • 9
  • 4
    Do you encounter an error? What is the error? Or does it simply not work as you intended? Try making a screenshot of what you want and what you acheived instead. – Dariusz Jul 03 '13 at 06:46
  • My guess as to what your question is would be you want some kind of pixel shader to overlay your base textures with noise - i advise you to reformulate your question to be more specific. I don't know much about pixel shaders, but without them i'd guess you need to create a new texture with your noisy tiles. – kutschkem Jul 03 '13 at 07:54
  • 1
    I see no where in your code where you actually try to use your noise. – Jyro117 Jul 03 '13 at 22:28
  • Im sorry , I am just confused , my goal is to generate perlin noise and then use it to render random 2d tile maps. I Am lost basically. – user2489897 Jul 05 '13 at 04:08
  • 1
    @user2489897 What is your exact problem; that the perlin noise might be wrong, or that you don't know how to convert the perlin noise to tiles? If you're not confident that the perlin noise is "right" then use it to generate a greyscale image (I can post code to achieve this if this is your problem) and have a look at it. It should look like hills/mountains looked at from the air – Richard Tingle Jul 05 '13 at 12:39

2 Answers2

4

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.

Community
  • 1
  • 1
Richard Tingle
  • 16,906
  • 5
  • 52
  • 77
3

I realize this is a somewhat old question, but I'd like to post my solution none the less since I found it hard to find working examples.

I was also researching this problem, at first I found your code useful as it seamed to work, in appearance, but when I wanted to change the size of the image the smooth noise would not scale appropriately and I could not find a way to fix your code.

After more research I found your implementation of SmoothNoise very dodgy and so I re-implemented it from a reliable source (http://lodev.org/cgtutor/randomnoise.html).

Here is my noise class, it can generate and work with any kind of noise :

package com.heresysoft.arsenal.utils;

public class Noise
{

    public static double[] blend(double[] noise1, double[] noise2, double persistence)
    {
        if (noise1 != null && noise2 != null && noise1.length > 0 && noise1.length == noise2.length)
        {
            double[] result = new double[noise1.length];
            for (int i = 0; i < noise1.length; i++)
                result[i] = noise1[i] + (noise2[i] * persistence);
            return result;
        }

        return null;
    }

    public static double[] normalize(double[] noise)
    {
        if (noise != null && noise.length > 0)
        {
            double[] result = new double[noise.length];

            double minValue = noise[0];
            double maxValue = noise[0];
            for (int i = 0; i < noise.length; i++)
            {
                if (noise[i] < minValue)
                    minValue = noise[i];
                else if (noise[i] > maxValue)
                    maxValue = noise[i];
            }

            for (int i = 0; i < noise.length; i++)
                result[i] = (noise[i] - minValue) / (maxValue - minValue);

            return result;
        }

        return null;
    }

    public static double[] perlinNoise(int width, int height, double exponent)
    {
        int[] p = new int[width * height];
        double[] result = new double[width * height];
        /*final int[] permutation = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
                                   190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
                                   20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
                                   220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
                                   200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147,
                                   118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
                                   154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
                                   218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192,
                                   214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
                                   72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180};*/

        for (int i = 0; i < p.length / 2; i++)
            p[i] = p[i + p.length / 2] = (int) (Math.random() * p.length / 2);//permutation[i];

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                double x = i * exponent / width;                                // FIND RELATIVE X,Y,Z
                double y = j * exponent / height;                                // OF POINT IN CUBE.
                int X = (int) Math.floor(x) & 255;                  // FIND UNIT CUBE THAT
                int Y = (int) Math.floor(y) & 255;                  // CONTAINS POINT.
                int Z = 0;
                x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
                y -= Math.floor(y);                                // OF POINT IN CUBE.
                double u = fade(x);                                // COMPUTE FADE CURVES
                double v = fade(y);                                // FOR EACH OF X,Y,Z.
                double w = fade(Z);
                int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,      // HASH COORDINATES OF
                        B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;      // THE 8 CUBE CORNERS,

                result[j + i * width] = lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, Z),  // AND ADD
                                                             grad(p[BA], x - 1, y, Z)), // BLENDED
                                                     lerp(u, grad(p[AB], x, y - 1, Z),  // RESULTS
                                                          grad(p[BB], x - 1, y - 1, Z))),// FROM  8
                                             lerp(v, lerp(u, grad(p[AA + 1], x, y, Z - 1),  // CORNERS
                                                          grad(p[BA + 1], x - 1, y, Z - 1)), // OF CUBE
                                                  lerp(u, grad(p[AB + 1], x, y - 1, Z - 1), grad(p[BB + 1], x - 1, y - 1, Z - 1))));
            }
        }
        return result;
    }

    public static double[] smoothNoise(int width, int height, double zoom)
    {
        if (zoom > 0)
        {
            double[] noise = whiteNoise(width, height);
            double[] result = new double[width * height];
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    double x = i / zoom;
                    double y = j / zoom;

                    // get fractional part of x and y
                    double fractX = x - (int) x;
                    double fractY = y - (int) y;

                    // wrap around
                    int x1 = ((int) x + width) % width;
                    int y1 = ((int) y + height) % height;

                    // neighbor values
                    int x2 = (x1 + width - 1) % width;
                    int y2 = (y1 + height - 1) % height;

                    // smooth the noise with bilinear interpolation
                    result[j + i * width] = fractX * fractY * noise[y1 + x1 * width]
                                            + fractX * (1 - fractY) * noise[y2 + x1 * width]
                                            + (1 - fractX) * fractY * noise[y1 + x2 * width]
                                            + (1 - fractX) * (1 - fractY) * noise[y2 + x2 * width];
                }
            }

            return result;
        }

        return null;
    }

    public static double[] turbulence(int width, int height, double zoom)
    {
        // http://lodev.org/cgtutor/randomnoise.html
        double[] result = new double[width * height];
        double initialZoom = zoom;

        while (zoom >= 1)
        {
            result = blend(result, smoothNoise(width, height, zoom), zoom);
            zoom /= 2.0;
        }

        for (int i = 0; i < result.length; i++)
            result[i] = (128.0 * result[i] / initialZoom);

        return result;
    }

    public static double[] whiteNoise(int width, int height)
    {
        double[] result = new double[width * height];
        for (int i = 0; i < width * height; i++)
            result[i] = Math.random();
        return result;
    }

    private static double fade(double t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    private static double lerp(double t, double a, double b)
    {
        return a + t * (b - a);
    }

    private static double grad(int hash, double x, double y, double z)
    {
        int h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
        double u = h < 8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
                v = h < 4 ? y : h == 12 || h == 14 ? x : z;
        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
    }

}

Here is an example of how to use the smoothNoise function :

        double[] data = Noise.normalize(Noise.smoothNoise(width, height, 32));

        for (int i = 0; i < data.length; i++)
            data[i] = 255*data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);

Here is an example of how to use the turbulence function :

        double[] data = Noise.normalize(Noise.turbulence(width, height, 32));

        for (int i = 0; i < data.length; i++)
            data[i] = 255*data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);

Here is an example of how to use the perlinNoise function :

        double[] data = Noise.normalize(Noise.perlinNoise(width, height, 7));

        for (int i = 0; i < data.length; i++)
            data[i] = 255 * data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);
Malbeth
  • 31
  • 2