Search code examples
javaperlin-noisenormal-distribution

Perlin Noise to Percentage


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).


Solution

  • 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