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.
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).
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.
Almost any parameter can be controlled by randomness, including Perlin noise. Let’s hook Perlin noise up to the eye color.
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
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.
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
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:
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.
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.
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).
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:
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
cos(), which produce regular oscillations.
Experimentation and progressive refinement is the key.
Perlin noise has the nice feature that it is “smooth” — small parameter changes result in small output changes. Uniform
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.