October 14

colors

More Randomness

We have seen several examples of randomness based on calling the random function. Either we use the number directly, or we use the numbers as offsets to create what is often called a “drunken walk” or Brownian motion. The last program example in Roger’s Notes for October 9 & 12 used this method.

Let’s redo this using other forms of randomness.

Perlin Noise

The noise function returns a smoothly varying value as a function of 1, 2, or 3 parameters that can be interpreted as time, space, or both. We saw Perlin noise in the terrain peak assignment. Below, we use it to move eyes. Since the noise function returns a value from 0 to 1, it’s important to scale the output appropriately (also the input matters — if you step through values rapidly, the output seems more chaotic).

perlin

var left, right;
var gdf;
var noiseParam = 0;
var noiseStep = 0.1;

function Ball() {
    this.ballX = 60;
    this.ballY = 50;
    this.draw = function() {
        var offset = noise(noiseParam);
        // map offset from 0-1 into -20 to +20
        offset = map(offset, 0, 1, -20, 20);
        fill(0, 222, 0);
        ellipse(this.ballX + offset, this.ballY, 50, 50);
    }
}
        
function setup() {
    createCanvas(200,200);frameRate(10);
    frameRate(10);
    left = new Ball();
    right = new Ball();
    right.ballX = 140;
}

function draw() {
    background(255,250,180);
    fill(100);
    ellipse(100, 80, 200, 200);
    left.draw();
    right.draw();
    noiseParam += noiseStep;
};

Notice that the eyes do the same thing, even though each calls noise and computes its “random” location independently. This emphasizes the fact that Perlin noise is a deterministic function: You put in the same parameters and you get out the same value. You only get change by changing the parameter. Since we use a global variable, noiseParam, and update it only in the main draw function, each Eye draw method passes the same parameter value to noise and gets the same result.

Other Parameters

Almost any parameter can be controlled by randomness, including Perlin noise. Let’s hook Perlin noise up to the eye color.

perlin

var left, right;
var gdf;
var noiseParam = 0;
var noiseStep = 0.1;

function Ball() {
    this.ballX = 60;
    this.ballY = 50;
    this.draw = function() {
        var offset = noise(noiseParam);
        // map offset from 0-1 into -20 to +20
        offset = map(offset, 0, 1, -20, 20);
        fill(100, noise(noiseParam * 0.2 + 100) * 255, noise(noiseParam * 0.2 + 200) * 255);
        ellipse(this.ballX + offset, this.ballY, 50, 50);
    }
}
        
function setup() {
    createCanvas(200,200);frameRate(10);
    frameRate(10);
    left = new Ball();
    right = new Ball();
    right.ballX = 140;
}

function draw() {
    background(255,250,180);
    fill(100);
    ellipse(100, 80, 200, 200);
    left.draw();
    right.draw();
    noiseParam += noiseStep;
};

I wanted the eye color to be based on different “random” values than the eye position. Recall that I cannot simply call noise again with the same argument or I’ll get the same value back. Instead, I used a useful trick: I added a fairly big number to the argument, calling noise(noiseParam + 100) and noise(noiseParam + 200). Since Perlin noise changes smoothly with the parameter values, a small offset would yield similar values, but by adding a “big” number like 100 or 200, I get fairly unrelated values. Since eye position and eye color are changed by increasing noiseParam at the same rate, the apparent rate of fluctuation of both color and position will be the same. I could, say, get eye color to change much more slowly by scaling noiseParam. In fact, that is the version shown above. Notice that the eye color seems to change more slowly than eye position. This demonstrates that you can change the apparent rate of change by changing the step size of the parameter to noise.

Uniform and Gaussian Noise

Uniform noise spreads values equally across a fixed range, e.g. from 0 to 1. Gaussian noise tends toward a certain average value (called the mean), with more or less random variation around the mean determined by the standard deviation. Let’s put some random points onto squares using uniform random noise and Gaussian noise.

noise

function setup() {
    createCanvas(400,200);
    background(0);
    // draw center line
    stroke(255);
    line(width/2, 0, width/2, height);
    for (var i = 0; i < 1000; i++) {
        // uniform random on the left
        point(random(width/2), random(height));
        // Gaussian noise on the right
        point(randomGaussian(width * 3/4, 30), randomGaussian(height/2, 30));
    }
}

function draw() {
};

Notice how the Gaussian case concentrates choices around the mean value (here, at the center of the right square) while uniform random spreads the points more-or-less equally everywhere. Also, there are probably some Gaussian points that are outside the bounds of the right square. You should test and reject out-of-bounds points if it matters.

Technique: “Gaussian” with Limits

Suppose you want a non-uniform distribution, but you want to restrict the range of values. You could simply replace x by min(x, limit), but then whenever x is out of bounds, it gets replaced by limit, so points could pile up on the boundary. Here’s what that looks like. Notice that a number of points show up 10 pixels from the edge due to the min/max functions:

noiseclip

function setup() {
    createCanvas(200,200);
    background(0);
    // draw center line
    stroke(255);
    for (var i = 0; i < 1000; i++) {
        // Gaussian noise for both x,y centered on canvas
        var x = randomGaussian(width/2, 50);
        var y = randomGaussian(height/2, 50);
        // force x, y within 10 pixels inside canvas edge
        x = max(10, min(width - 10, x));
        y = max(10, min(height - 10, y));
        point(x, y);
    }
}

function draw() {
};

A better idea is to simply keep picking random numbers until you get something in bounds. This is a great place to use a while loop. The essence of the approach is this: “Pick a value. While value is out of bounds, pick a new value.” Notice the points piled up near the borders are gone here. We simply picked new values.

noisewhile

function setup() {
    createCanvas(200,200);
    background(0);
    // draw center line
    stroke(255);
    for (var i = 0; i < 1000; i++) {
        // Gaussian noise for both x,y centered on canvas
        var x = randomGaussian(width/2, 50);
        while (x < 10 || x > width - 10) {
            // test was true, so run the loop body...
            // x is out of bounds, try again
            x = randomGaussian(width/2, 50);
        }
        // test was false, so now x must be in bounds

        // compute y just like x: keep looping until value is in bounds
        var y = randomGaussian(height/2, 50);
        while (y < 10 || y > height - 10) {
            y = randomGaussian(height/2, 50);
        }

        point(x, y);
    }
}

function draw() {
};

Gaussian Noise with Colors

You could get a similar effect by just not drawing points that are out of bounds. This would result in drawing fewer points, which is probably OK for this application, but maybe you need a Gaussian-like distribution for color selection, so simply ignoring bad values is not an option. Here’s some code that adds limited Gaussian noise to colors. Note that mouseX controls the standard deviation of the Gaussian values.

colors

function setup() {
    createCanvas(200,200);
    frameRate(1);
}

function draw() {
    var range = 50; // random r ranges from -100 to 100
    background(0);
    noStroke();
    for (var x = 0; x < width; x += 5) {
        for (var y = 0; y < height; y += 5) {
            var r = randomGaussian(0, mouseX / 5);
            while (r < -range || r > range) {
                r = randomGaussian(0, 20);
            }
            fill(128, 200 + r, 180 - r); // add some random to G & B
            rect(x, y, 5, 5);
        }
    }
};

Skewing with Power

Suppose you want to add an offset to some number to achieve some artistic variation. You want the offset to be small (normally) but sometimes much larger. Consider the simple expression x * x. If x is in the range 0 to 1, x * x will be smaller than x. E.g. if x = 1/2, then x*x = 1/4. However, if x=1, then x*x = 1. Thus, x*x has the same range as x, but the values are skewed toward zero. We can get an even more extreme skew toward zero by computing x*x*x, or x*x*x*x, etc. Multiplication is cheap, and the more multiplies, the less likely it is to get a large number (i.e. close to 1).

Power

Instead of writing x*x or x*x*x, we can write Math.pow(x, 2) or Math.pow(x, 3). The pow function has the advantage that we can use a parameter for the exponent. If you are not familiar with the “power” function or exponents, please review the subject and see the reference for Math.pow. E.g. instead of Math.pow(x, 2), we can write Math.pow(x, skew), where skew is a variable we can easily adjust to get the effect we want.

Here is an example: The goal is to occasionally produce a bright circle. The skew parameter can be varied with the mouse:

power

var skew = 4;

function setup() {
    createCanvas(200,200);
    frameRate(10);
}

function draw() {
    background(0);
    skew = mouseX;
    noStroke();
    skew = map(mouseX, 0, width, 0, 10);
    text("move mouse to change skew", 10, 10);
    text("skew = " + skew.toFixed(2), 10, 25);
    fill(Math.pow(random(1), skew) * 255);
    ellipse(width/2, height/2, width/2, height/2);
}

Playing with Random Functions

There’s no “right” or “wrong” way to use random values. Uniform random is just that — random. As you add structure to randomness by shaping the distribution, applying maximum and minimum limits, and perhaps other rules, constraints, and behaviors, you progressively move from “random” to “generative” or “algorithmic” — i.e. deliberate artistic expression.

Combining functions to get complex behavior is standard fare. For example, if you raise Perlin noise values to some power using Math.pow(), you can achieve the smoothness of Perlin noise while skewing the values to mostly near-zero values.

You can also add non-random variation using our old friends sin() and cos(), which produce regular oscillations.

Experimentation and progressive refinement is the key.

Smooth Motion

Perlin noise has the nice feature that it is “smooth” — small parameter changes result in small output changes. Uniform random and guassianRandom functions jump around when you pick a new value. If you want to use random numbers for controlling positions, whether you are drawing lines or creating animation, you probably want smooth transitions as opposed to jumps. One way to do this in drawing is to use curves and Bezier splines to do the smooth interpolation. With animation, other techniques can be used. Mainly, you can use random points to establish targets or attractors, and then move the object or control parameter (e.g. an angle) smoothly in the direction of the target/attractor. We will spend some time soon on physics, gravity, attraction, acceleration, and smooth motion.