Skip to content

Instantly share code, notes, and snippets.

@bit-hack
Last active September 27, 2019 10:52
Show Gist options
  • Save bit-hack/012b30d17396319c025fb0a55698a44e to your computer and use it in GitHub Desktop.
Save bit-hack/012b30d17396319c025fb0a55698a44e to your computer and use it in GitHub Desktop.
Be less random with rand()

Be less random with rand()

By Aidan Dodds // twitter

Introduction

It should come as no real surprise that most procedural content generation (PCG) systems are underpinned by a good random number generator. Most programming languages provide a means to generate random numbers; traditionally via a rand() function. This function typically generates uniform random numbers, which is to say, any number has the same likelihood of being returned as any other. If you are looking to implement your own then a nice starting point may be the Xorshift PRNG.

Procedural content however is less about randomness, and rather more about building upon sources of randomness to create unique and artistic content. In this article, we will look at rand() and see how it can be extended into something more versatile.

I feel a quick disclaimer is in order however; I am a programmer, not a statistician, so while the following techniques have served me well for my PCG needs, there is a good chance my math or terminology is wrong...

Starting point

As a starting point, lets assume that we have a function randf() that returns a uniform number in the range [-1, +1]. Such a function may already be provided by your language but is generally trivial to implement, for instance:

function randf()
    # where rand() returns a random unsigned integer
    return 1.0f - float(rand() % 4096) / 2048;
end

Uniform distributions however are often not the best fit artistically for a game or PCG system. It can be very useful to have control over the probability of our generated values. So lets look at some alternatives to the uniform distribution, and how to produce them (in pseudo code form):

1D distributions

Triangular distribution:

By taking the average of two random numbers it can be shown that there is a much stronger chance of a value near 0.0 being produced than that of 1.0 or -1.0. Such a distribution can be useful when you want to add a some variance to data with a few large variations and substantially more small variations.

function rand_triangle()
    return (randf()+randf()) / 2.0
end
Pinch distribution

The pinch distribution as I call it (because i do not know the correct term) is somewhat like an extreme version of the triangle distribution. values near 0.0 are very probable where as it is rare that values near 1.0 and -1.0 will be returned. This distribution has great results when used for adding a little variation to firing lines for example. I have also had nice results using this distribution to effect the direction of each element in a particle system.

function rand_pinch()
    return randf() * abs(randf())
end
Gaussian distribution

A Gaussian distribution (or normal distribution as its also known) can be constructed, and is very distinctive with its bell like appearance. Interestingly the higher to number of rounds the better the approximation becomes. This distribution can be nice when you want a good range of values with a few larger outliers. This could make a nice basis for generating good looking star systems.

function rand_gaussian()
    int rounds = 4
    float sum = 0.0
    for 1 to rounds
        sum += randf()
    end
    return sum / rounds
end

2D and 3D distributions

Random 2D vector in a circle

When writing procedural generation systems it is often desirable to be able to generate a 2D or 3D vector that falls uniformly within a circle or sphere. That is to say the vectors direction is random, and its magnitude ranges from (0.0, 1.f]. This can be useful for applications such as random sampling around a point, making random walks and stochastic approximations like ambient occlusion.

function rand_circle()
    float x = 0.0, y = 0.0
    while (True)
        x = randf()
        y = randf()
        if ((x*x + y*y) <= 1.0)
            return (x, y)
        end
    end
end

Random unit 2D vector

Generating a good random unit vector (vector with length 1.0) can be a little more trick then it first seems. The most obvious solution would be to randomize the x, y and z components and then normalize the vector; which however produces a less then ideal vector since it will be biased towards diagonals.

We can generate an unbiased vector by starting with our random_circle() function before normalizing it.

function rand_unit_vector()
    return vector_normalize(rand_circle())
end
Random 3D vectors in a sphere

The same approach we took for generating 2D vectors can easily be extended to three dimensions as follows.

function rand_sphere()
    fload x = 0.0, y = 0.0, z = 0.0
    while (True)
        x = randf()
        y = randf()
        z = randf()
        if ((x*x + y*y + z*z) <= 1.0)
            return (x, y, z)
        end
    end
end

Like we did before, if we normalize this vector then we can produce an unbiased unit 3D vector.

In closing

A few relatively simple techniques to generate more interesting random numbers have been presented. Where and how they are applied is still firmly where the artistic element of procedural generation lies. Like an artist however, its always good to have more brushes to paint with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment