New perlin noise

#1

Hi everyone,

While working on a project I realized that the perlin noise generated with the noise() function could appear really “blocky” (see comparaison picture at the end) so I decided to implement my own version.

I had already done it some times ago in that thread:

But to be honest I was mostly copy-pasting the code I ported my version from.

Anyway, this time I really wanted to fully understand it and I found a nice paper online that I advise anyone interested in how perlin noise works to read:
http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf

Keeping the foundation of @jeremydouglass’s work in the cross-language random numbers thread above, I built on top a version of the perlin noise generetor that is getting read of that “blocky effect”.

I’m not sure why it is blocky though, so maybe I was using it wrong in the first place…

Here the code with the comparaison of the 2 versions:

void setup() {
  size(1000, 500);
  
  PRNG prng = new PRNG();
  
  noiseDetail(1,1);
  float noiseScale = 50;

  loadPixels();
  for (int x = 0; x < 500; x++) {
    for (int y = 0; y < height; y++) {      
      float n1 = prng.perlin(noiseScale * (float(x) / height), noiseScale * (float(y) / width));
      float n2 = noise(noiseScale * (float(x) / height), noiseScale * (float(y) / width));
      
      pixels[y * width + x] = color(n1 * 255);
      pixels[y * width + x + 500] = color(n2 * 255);
    }
  }
  updatePixels();
}


class PRNG {
  private long seed;
  private long a;
  private long c;
  private long m32;
  private int[] p;
  private PVector gradients[];
  private final float perlinBound = 1. / sqrt(2);


  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************

  /*
   * All code in this section was written by Jeremy Douglass
   * You can see the original thread here: https://discourse.processing.org/t/cross-language-random-numbers/3474
   */


  /*
   * Constructor
   * 
   * Defaults to current system time in milliseconds.
   */
  PRNG() {
    this(System.currentTimeMillis());
  }


  /** 
   * @param  seed  starting seed value 
   */
  PRNG(long seed) {
    this.a = 1664525;
    this.c = 1013904223;
    this.m32 = 0xFFFFFFFFL;
    this.randomSeed(seed);
    initPerlin();  // Added to support the perlin noise
  }


  /**
   * Updates seed based on past value, returns new value.
   * 
   * @return  unsigned 32bit value stored in a long
   */
  long nextLong() {
    this.seed = this.seed * a + c & m32;
    return this.seed;
  }


  /**
   * Returns next long wrapped to the max integer value.
   * Implemented so that calls to nextInt or nextLong stay in sync,
   * the next seed internally is always updated to the next long.
   * 
   * @return  positive 
   */
  int nextInt() {
    return (int)(this.nextLong()%Integer.MAX_VALUE);
  }


  /**
   * Set the current seed.
   * 
   * @param  newSeed  the new seed value
   */
  void randomSeed(long newSeed) {
    this.seed = newSeed%Integer.MAX_VALUE;
  }


  /**
   * @return  a random float between 0 and 1
   */
  float random() {
    return random(0, 1);
  }


  /**
   * @param   max  maximum value to return
   * @return  a random float between 0 and max
   */
  float random(float max) {
    return random(0, max);
  }  


  /**
   * @param   min  minimum value to return
   * @param   max  maximum value to return
   * @return       a random float in the specified range (min, max)
   */
  float random(float min, float max) {
    return map(this.nextInt(), 0, Integer.MAX_VALUE, min, max);
  }


  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************
  // ***********************************************************************************************************************************************  






  /*
   * I built the perlin noise on top of the work above
   */


  /*
   * @return  a random int between 0 included and maxInt excluded
   */
  int nextInt(int maxInt) {
    return nextInt() % maxInt;
  }


  /*
   * Initialize the parameters of the perlin noise
   * 
   * 16 unit gradients evenly spaced are created
   * Those gradients will be place on a grid randomly
   *
   * A permutation array p is randomly created
   * This array allows to randomly place the gradients on the grid 
   */
  private void initPerlin() {
    // Create 16 gradients evenly spaced around the unit circle
    gradients = new PVector[16];
    float stepSize = TWO_PI / 16;
    for (int i = 0; i < 16; i++) {
      gradients[i] = PVector.fromAngle(i * stepSize);
    }

    // Create a pseudo-random permutation array that will be used to randomly place the gradients on the grid
    int[] partA = new int[256]; // Needs to be a multiple of to give the same chance to all the gradients;
    int[] partB = new int[256]; // Needs to be a multiple of to give the same chance to all the gradients;

    for (int i = 0; i < 256; i++) {
      partA[i] = i;
      partB[i] = i;
    }

    shuffleArray(partA);
    shuffleArray(partB);

    // p consists of 2 parts to avoid beeing out of bounds values in the perlin code
    p = new int[512];
    for (int i = 0; i < 256; i++) {
      p[i] = partA[i];
      p[i + 256] = partB[i];
      ;
    }
  }


  /*
   * @param   x  x coordinate of the perlin noise
   * @param   y  y coordinate of the perlin noise
   * @return       a random float between 0 and 1
   */
  float perlin(float x, float y) {
    // Get the top left grid corner coordinates
    int i = (int)x & 255; // = x % 256
    int j = (int)y & 255; // = y % 256

    // Get the gradients used in the 4 surrounding grid corners
    int gradTL = ((p[p[p[      i] +       j]]) & 15); // = % 16   -   Top left gradient    (in processing coordonate system)
    int gradTR = ((p[p[p[incr(i)] +       j]]) & 15); // = % 16   -   Top right gradient   (in processing coordonate system)
    int gradBL = ((p[p[p[      i] + incr(j)]]) & 15); // = % 16   -   bottom left gradient (in processing coordonate system)
    int gradBR = ((p[p[p[incr(i)] + incr(j)]]) & 15); // = % 16   -   Top left gradient    (in processing coordonate system)

    // get the delta from the top left grid corner
    float u = x - (int)x;
    float v = y - (int)y;

    // Compute the dot products between the gradients at the 4 surrounding grid corners and the 4 vectors starting at the corners and heading towards the point (x, y)
    float dotProdTL = dotProd(gradients[gradTL], u, v);
    float dotProdTR = dotProd(gradients[gradTR], u - 1, v);
    float dotProdBL = dotProd(gradients[gradBL], u, v - 1);
    float dotProdBR = dotProd(gradients[gradBR], u - 1, v - 1);

    // Interpolate the values on the x-axis
    float topAverage = lerpValues(dotProdTL, dotProdTR, u); 
    float bottomAverage = lerpValues(dotProdBL, dotProdBR, u);

    // Interpolate and the y-axis and return the result
    return map(lerpValues(topAverage, bottomAverage, v), -perlinBound, perlinBound, 0, 1);
  }


  /*
   * @param   x           x coordinate of the perlin noise
   * @param   y           y coordinate of the perlin noise
   * @param   nbOFreq     The number of frequencies to add on top of another
   * @param   freqFactor  The amount by which to change the next frequency
   * @param   ampFactor   The amount by which to change the next amplitude
   *
   * @return              a random float between 0 and 1
   */
  float mixPerlin(float x, float y, int nbOFreq, float freqFactor, float ampFactor) {
    float result = 0;
    float freq = 1;
    float amp = 1;
    final float maxValue = (1 - pow(ampFactor, nbOFreq)) / ( 1 - ampFactor);

    for (int i = 0; i < nbOFreq; i++) {
      result += perlin(x * freq, y * freq) * amp;
      amp *= ampFactor;
      freq *= freqFactor;
    }

    return result/maxValue;
  }


  /*
   * @param   x    x coordinate of the perlin grid point
   * @param   y    y coordinate of the perlin grid point
   *
   * @return       The gradient used at that perlin grid point
   */
  PVector getGrad(int x, int y) {
    int i = x & 255; // = x % 256
    int j = y & 255; // = y % 256

    int gradTL = ((p[p[p[i]+j]]) & 15);
    return gradients[gradTL];
  }


  /*
   * @param   a    The start point
   * @param   b    The end point
   * @param   t    The value to which interpolate 
   *
   * @return       The interpolation at t between a and b. The function used is not linear in order to get continuous second derivative 
   */
  private float lerpValues(float a, float b, float t) {
    return a + fade(t) * (b - a);
  }


  /*
   * @param   t    The value at which evaluate the function
   *
   * @return       The value of the function 6 t^5 - 10 t^4 + 15 t^3 
   */
  private float fade(float t) {
    return t * t * t * ( t * ( t * 6 - 15 ) + 10 );
  }


  /*
   * @param   v    The first vector
   * @param   x    The x componant of the second vector
   * @param   y    The y componant of the second vector
   *
   * @return       the dot product of the 2 vectors
   */
  private float dotProd(PVector v, float x, float y) {
    return v.x * x + v.y * y;
  }


  /*
   * Increase a value by 1 and make sure it does not go above 255
   * if so return 0 instead
   */
  private int incr(int a) {
    return a > 254 ? 0 : a + 1;
  }


  /*
   * Function written by PhiLho here: https://stackoverflow.com/questions/1519736/random-shuffling-of-an-array
   * Shuffle the passed array randomly
   */
  private void shuffleArray(int[] ar)
  {
    for (int i = ar.length - 1; i > 0; i--)
    {
      int index = nextInt(i + 1);
      // Simple swap
      int a = ar[index];
      ar[index] = ar[i];
      ar[i] = a;
    }
  }
}

Hope you like it =)

6 Likes