OPENRNDR & Processing - Noise fields & leaving trails

Noise fields & leaving trails

This example is based on a very short program I left in the Processing forum in 2018. It compares side by side two different noise functions.

If you want to compare it to the version below you can open it in another window.

The algorithm is very simple:

  1. Current point = random point on the screen.
  2. Use the coordinates of that point scaled down to look at a Perlin / Value / Simplex noise function.
  3. Map the obtained value to an angle.
  4. Use the angle to alter current point moving in polar coordinates.
  5. Draw a point at current point with low opacity.
  6. Go to 2.

This is the OPENRNDR version I came up with:

imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.rgba
import org.openrndr.extra.noclear.NoClear
import org.openrndr.extra.noise.Random
import org.openrndr.math.Polar
fun main() = application {
    configure { width = 800; height = 800 }
    program {
        val zoom = 0.03
        backgroundColor = ColorRGBa.WHITE
        extend(NoClear())
        extend {
            drawer.fill = rgba(0.0, 0.0, 0.0, 0.01)
            drawer.points(generateSequence(Random.point(drawer.bounds)) {
                it + Polar(
                    180 * if (it.x < width / 2)
                        Random.value(it * zoom)
                    else
                        Random.simplex(it * zoom)
                ).cartesian
            }.take(500).toList())
        }
    }
}

NoClear
By default most graphics frameworks do clear the screen on every animation frame. Processing is unique in not doing that, so it acts more like a canvas: one can keep painting things on that canvas and unless background() is called, it stays there.

There is a simple extension to achieve that behavior in OPENRNDR, called orx-no-clear. By using it we can keep painting over what is already in the canvas with very low opacity to achieve a slowly evolving design. The guide explains how to enable extensions.

generateSequence
Instead of calling drawer.point() multiple times, I decided to call drawer.points() once to make it more efficient by using batching. This reduces the number of calls to the GPU from hundreds to just a few. Probably not necessary in this case, but a good exercise :slight_smile:

For the sake of experimenting, I decided to use generateSequence which I never used before. The first argument is the initial value of the sequence, in this case a random point on the screen. The second argument is a function that returns a new point based on it, the previous point. Sequences are potentially endless, therefore I .take(500) items from that sequence and convert it to a list which makes drawer.points() happy.

A more straightforward version without batching nor sequences might look like this:

        // unchanged part ommitted
        extend {
            drawer.fill = rgba(0.0, 0.0, 0.0, 0.01)
            var pos = Random.point(drawer.bounds)
            repeat(500) {
                drawer.point(pos)
                pos += Polar(
                    180 * if (pos.x < width / 2)
                        Random.value(pos * zoom)
                    else
                        Random.simplex(pos * zoom)
                ).cartesian
            }
        }

Other noise functions
Random provides several functions you can play with: Random.value(), Random.simplex(), Random.cubic(), Random.perlin() and more.

Moving through the noise space
We can make a tiny change to the original program above to make the noise field change over time:

Random.simplex(pos.vector3(z = seconds) * zoom)

What that does is to create a Vector3 out of pos (a Vector2) setting the z component to the current time. That produces a more blurry image:

:point_down: Share your questions and comments below | :mag_right: Find other OPENRNDR & Processing posts

5 Likes