Perlin Noise - 2D Map

Perlin Noise is a kind of algorithmic random value generator that, unlike pure random methods where each value is unrelated to the previous or next, generates values in a way that whenever you look at one particular value, you know the adjecent values are not radically far of.

I highly recommend this series by Daniel Shiffman of The Coding Train on Perlin Noise for some high energy, high silliness but super informative information on Perlin Noise.

In fact - and without surprise - it was mostly the Coding Train that nudged me to get off my ass and start these experiments (done and redone by thousands of people throughout the internet).

In this particular exercise I’m mapping Perlin Noise onto a 2D space, so effectively you’re seeing the noise values generated as if we’re looking top down onto a surface. What you’re seeing is a highly detailed rendition of the perlin noise values in 2D , but if you click on the image I’ll start to iterate those values, as the rendering is slow (and until I know how to improve things) I’m evaluating and lowering resolution to try and not kill your computer :)

The code is shown bellow, it is literally the code that generates whatever image you’re seeing and I’ll try to work out more interesting experiments, so come back now and then.

All the code that runs this experiment is bellow, apart from any external libraries (i.e. p5.js)

'use strict'

// Avoiding Global Mode: https://github.com/processing/p5.js/wiki/Global-and-instance-mode
const perlin_noise = ( sketch ) => {

  // This is the element where we'll create our P5 canvas
  let container = document.getElementById('sketch-holder')

  let start = 0         // Where in the Perlin Noise graph we'll begin looking for values
  let increment = 0.005  // How close together the values will be
  let gridSizeInPixels = 8

  sketch.setup = () => {
    container.classList.add('interactive')  // Declare this experiment is interactive

    var canvas = sketch.createCanvas(container.clientWidth, container.clientHeight)
    canvas.parent(container)
    canvas.mouseClicked(clickHandler)
    canvas.touchStarted(clickHandler)

    sketch.background(240, 240, 240)
    sketch.noiseDetail(8, 0.6)
    sketch.frameRate(50)

    sketch.noLoop()
  }

  sketch.windowResized = () => {
    sketch.resizeCanvas(container.clientWidth, container.clientHeight)
    sketch.draw()
  }

  function clickHandler() {
    if (sketch.isLooping()) {
      sketch.noLoop()
    } else {
      sketch.loop()
      gridSizeInPixels = 8
    }
  }

  sketch.draw = () => {
    // When looping, add in controls to adjust to low framerates, including
    // stopping the loop eventually - otherwise, render at maximum quality
    if (sketch.isLooping() ) {
      // Every 25 frames we take a little sample and adjust grid size to improve framerate
      // if we absolutely can't, then we stop the loop
      if (sketch.frameCount % 25 == 0) {
        if (sketch.frameRate() < 20) {
          if (sketch.pixelDensity() > 1) {
            sketch.pixelDensity(1) // Go with less pixel density
          } else {
            gridSizeInPixels += 2
          }
        } else if (gridSizeInPixels > 2 && sketch.frameRate() > 35 ) {
          gridSizeInPixels -= 1
        } else if (gridSizeInPixels > 10 && sketch.frameRate() < 5) {
          gridSizeInPixels = 1
          sketch.noLoop()
        }
      }
    } else {
      gridSizeInPixels = 1
    }

    var z_offset = start
    var density = sketch.pixelDensity()

    var x = 0
    var y = 0
    var index = 0
    var color = 0

    sketch.noStroke()

    // This maps noise values onto a 2D space
    for(x = 0; x < sketch.width * density; x += gridSizeInPixels * density) {
      for(y = 0;  y < sketch.height * density; y += gridSizeInPixels * density) {
        index = (x + y * sketch.width * density) * 4;
        color = sketch.map(
          sketch.noise(
            x / density * increment,  // Use the X as index but transform to smaller steps in the noise space
            y / density * increment,  // Use the Y as index but transform to smaller steps in the noise space
            z_offset
          ),
          0,                          // Noise lower bound value
          1,                          // Noise upper bound value
          0,                          // Map lower to Full black
          255                         // Map upper to Full white
        )

        sketch.fill(color)
        sketch.rect(x, y, gridSizeInPixels * density, gridSizeInPixels * density)
      }
    }

    start += increment
  }
}

// Wait for everything to load
if (document.readyState === 'complete') {
  new p5(perlin_noise)
} else {
  window.onload = (event) => {
    new p5(perlin_noise)
  }
}