6

I'm coding a map generator based on a perlin noise and ran into a problem:

Lets say I would want 30% water and 70% dirt tiles. With a usual random generator there is no problem:

tile = rnd.nextFloat() < 0.7f ? DIRT : WATER;

But a perlin noise is normal distributed (ranges from -1 to 1, mean at 0) so it's not that easy.

Does anyone know a way to transform a normal to an uniform distribution or a different way I could get a percentage from a noise value?

EDIT: The 70% are just an example, I'd want to be able to use any value dynamically, at best with 0.1% precision.

EDIT2: I want to transformate perlin noise to a uniform distribution, not to normal (which it already is alike).

DiddiZ
  • 625
  • 8
  • 17
  • 1
    perlin maps are basically height maps. unless you've got rivers, you pick a 'height' and say anything below it is water. do some stats on the generated map and figure out what height has 30% of the points "below" it. – Marc B Jul 14 '12 at 22:15
  • I'd like to avoid statistics and rather have a way to figure that out dynamically – DiddiZ Jul 14 '12 at 22:20
  • See [Uniform distribution from a fractal Perlin noise function in C#](http://stackoverflow.com/questions/9549851/uniform-distribution-from-a-fractal-perlin-noise-function-in-c-sharp) and [Function to transform empirical distribution to a uniform distribution in Matlab?](http://stackoverflow.com/questions/11317744/function-to-transform-empirical-distribution-to-a-uniform-distribution-in-matlab) – Markus Jarderot Jul 14 '12 at 22:55
  • Thanks for the links, the first sounds exactly like what I want, but oddly the accepted answer seems to handle the opposite (uniform -> normal) – DiddiZ Jul 14 '12 at 23:05
  • Check out http://stackoverflow.com/questions/75677/converting-a-uniform-distribution-to-a-normal-distribution The standard rnd is a uniform distribution. – Rob Trickey Jul 14 '12 at 23:18
  • Unfortunately, rnd is really ugly for a map generator. – DiddiZ Jul 14 '12 at 23:27

6 Answers6

8

If you want to get exactly 30% water (or some other specified value), you could do this.

  1. Generate your height-map.
  2. Place all the height-values into a list.
  3. Sort the list.
  4. Pick the value, that appears 30% into the list, as your water-level.
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
  • TMHO this is by far the most simple and efficient solution the have been mentioned here ! – AsTeR Jul 15 '12 at 08:44
5

Here's an analytic solution which doesn't depend on keeping data around, and is continuous. Following the method described here, I produced a histogram of perlin noise values, then approximated the continuous distribution function by summing the histogram, so cdf(x)

cdf(x) = sum(histogram[i] for all i < x)

Then I used Wolfram Alpha to approximate cdf(x) with a fifth-degree polynomial. This gave me this function:

function F(x) { return (((((0.745671 * x + 0.00309887) * x - 1.53841) * x - 0.00343488) * x + 1.29551) * x) + 0.500516;

x^5+0.00309887 x^4-1.53841 x^3-0.00343488 x^2+1.29551 x+0.500516 u = (u + 0.002591009999999949) / 1.0055419999999997; // cross (0,0) and (1,1)

F(x) = 0.745671 x^5 + 0.00309887 x^4 - 1.53841 x^3 - 0.00343488 x^2 + 1.29551 x + 0.500516

Now F(perlin.noise2(...)) gets reasonably close to being uniformly distributed.

This function doesn't quite pass through points (-1,0) and (1,1) so you could correct it as

F1(x) = (F(x) + 0.002591009999999949) / 1.0055419999999997

The function also strays just above 1 near x = 1 and just below 0 near x = -1, so you should clamp it between 0 and 1 if that matters to you.

F2(x) = max(min(F1(x), 1), 0)

(I'll leave this pretty terse unless someone who wants more detail appears. Leave a comment if so.)

Grumdrig
  • 16,588
  • 14
  • 58
  • 69
4

The Perlin noise distribution is only gaussian like, it's not truly a normal distribution.

Furthermore, the peak is very narrow, with the standard deviation being around 0.1 (I can't find an exact figure).

Just pick your threshold at ~ 0.1, and that should give you approximately 70% values below that, and 30% above.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • 2
    Any idea how to calculate the threshold dynamically? – DiddiZ Jul 14 '12 at 23:06
  • I don't think you can "calculate" it - you'd have to take a suitably large number of test samples and then find out what threshold gives you the desired percentile. – Alnitak Jul 14 '12 at 23:12
4

A solution I figured out: Firstly, I generate 100,000,000 perlin noises and store them in an array. I sort it, and afterwards I can take every 10,000 value as a threshold for one per mille. Now I can hardcode these thresholds, so I've just an array with 1,000 floats for lookup at runtime.

Advantages:

It's really fast, as it's just one array access at runtime.

Drawbacks:

If you change the algorithm, you have to regenerate your threshold array. Secondly, the mean scales to about 10 per mille, making a 50% threshold either 49.5% or 50.5% (depending on whether you use < or <= comperator). Thirdly, the increased memory footprint (4kb with per mill precision). You can reduce it by using percent precision or a logarithmic precision scale.

Generation code:

final PerlinNoiseGenerator perlin = new PerlinNoiseGenerator(new Random().nextInt());

final int size = 10000; //Size gets sqared, so it's actually 100,000,000

final float[] values = new float[size * size];
for (int x = 0; x < size; x++)
    for (int y = 0; y < size; y++) {
        final float value = perlin.noise2(x / 10f, y / 10f);
        values[x * size + y] = value;
    }
System.out.println("Calculated");

Arrays.sort(values);
System.out.println("Sorted");

final float[] steps = new float[1000];
steps[999] = 1;
for (int i = 0; i < 999; i++)
    steps[i] = values[size * size / 1000 * (i + 1)];
System.out.println("Calculated steps");

for (int i = 0; i < 10; i++) {
    System.out.println();
    for (int j = 0; j < 100; j++)
        System.out.print(steps[i * 100 + j] + "f, "); //Output usuable for array initialization
    System.out.println();
    System.out.println();
}

Lookup code:

public final static float[] perlinThresholds = new float[]{}; //Initialize it with the generated thresholds.

public static float getThreshold(float percent) {
    return perlinThresholds[(int)(percent * 1000)];
}

public static float getThreshold(int promill) {
    return perlinThresholds[promill];
}

X
DiddiZ
  • 625
  • 8
  • 17
  • 1
    would you care to share that resulting array somewhere? Maybe someone can figure out a "good enough" formula for its real cumulative distribution function. – Alnitak Jul 15 '12 at 13:09
0

Simply add 1 and divide by 2 ? This would give you a distribution centered on 0.5 and from 0 to 1.

EDIT : your want a threshold splitting 70% vs 30% you have to use the cumulative distribution function of the normal law, you just have to find the x such as the probability of being under x is 0.7. Please note that this work for normal distribution only, if you distribution is always between -1 and 1, it's not a normal distribution. Normal distribution output interval is supposed to be -∞ and +∞. The solution mentionned by @paxinum could be simpler that doing the computation yourself.

AsTeR
  • 7,247
  • 14
  • 60
  • 99
  • Yeah, but it's still normal distributed and I don't know what 70% would be. – DiddiZ Jul 14 '12 at 22:17
  • Normal distributed means you can "normalize it" by substracting average dividing by standard deviation. Then a table (like the one mentioned by @Paxinum ) gives you the threshold to consider (which is 0.53 for having a split at 70%). – AsTeR Jul 19 '12 at 11:11
0

Well, if it is almost Gaussian, then 70% would be everything below 0.75, see this table: http://www.roymech.co.uk/Useful_Tables/Statistics/Statistics_Normal_table.html

Per Alexandersson
  • 2,443
  • 1
  • 22
  • 28