Уровень шума Перлина в процентах

Я кодирую генератор карт на основе шума Перлина и столкнулся с проблемой:

Допустим, мне нужны плитки с 30% воды и 70% с грязью. С обычным генератором случайных чисел проблем нет:

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

Но шум Перлина имеет нормальное распределение (диапазон от -1 до 1, среднее значение 0), так что это не так просто.

Кто-нибудь знает способ преобразовать нормаль в равномерное распределение или другой способ получить процент от значения шума?

РЕДАКТИРОВАТЬ: 70% - это просто пример, я бы хотел иметь возможность динамически использовать любое значение, в лучшем случае с точностью 0,1%.

РЕДАКТИРОВАТЬ2: я хочу преобразовать шум Перлина в равномерное распределение, не в нормальное (что уже похоже).


person DiddiZ    schedule 14.07.2012    source источник
comment
Карты Perlin - это в основном карты высот. если у вас нет рек, вы выбираете «высоту» и говорите, что все, что ниже нее, - это вода. сделайте некоторую статистику на сгенерированной карте и выясните, на какой высоте находится 30% точек под ней.   -  person Marc B    schedule 15.07.2012
comment
Я бы хотел избежать статистики и лучше иметь способ вычислить это динамически.   -  person DiddiZ    schedule 15.07.2012
comment
Спасибо за ссылки, первый звучит именно так, как я хочу, но, как ни странно, принятый ответ, кажется, обрабатывает противоположное (равномерное - ›нормальное)   -  person DiddiZ    schedule 15.07.2012
comment
Ознакомьтесь с http://stackoverflow.com/questions/75677/converting-a-uniform-distribution-to-a-normal-distribution Стандартный rnd - это унифицированный дистрибутив.   -  person Rob Trickey    schedule 15.07.2012
comment
К сожалению, rnd действительно уродлив для генератора карт.   -  person DiddiZ    schedule 15.07.2012


Ответы (6)


Если вы хотите получить ровно 30% воды (или другое указанное значение), вы можете это сделать.

  1. Создайте свою карту высот.
  2. Поместите все значения высоты в список.
  3. Отсортируйте список.
  4. Выберите значение, которое появляется в списке 30% в качестве вашего уровня воды.
person Markus Jarderot    schedule 14.07.2012
comment
TMHO - это, безусловно, самое простое и эффективное решение, которое здесь упоминалось! - person AsTeR; 15.07.2012

Распределение шума Перлина только гауссово, это не совсем нормальное распределение.

Кроме того, пик очень узкий, со стандартным отклонением около 0,1 (я не могу найти точную цифру).

Просто выберите порог ~ 0,1, и вы получите приблизительно 70% значений ниже этого и 30% выше.

person Alnitak    schedule 14.07.2012
comment
Есть идеи, как рассчитать порог динамически? - person DiddiZ; 15.07.2012
comment
Я не думаю, что вы можете рассчитать это - вам нужно будет взять достаточно большое количество тестовых выборок, а затем выяснить, какой порог дает вам желаемый процентиль. - person Alnitak; 15.07.2012

Решение, которое я придумал: во-первых, я генерирую 100000000 шумов Перлина и сохраняю их в массиве. Я сортирую его, и впоследствии я могу принять каждые 10 000 значений в качестве порогового значения для одной промилле. Теперь я могу жестко запрограммировать эти пороги, так что у меня есть только массив из 1000 чисел с плавающей запятой для поиска во время выполнения.

Преимущества:

Это действительно быстро, поскольку во время выполнения требуется всего лишь один доступ к массиву.

Недостатки:

Если вы измените алгоритм, вам придется заново сгенерировать массив пороговых значений. Во-вторых, среднее значение увеличивается примерно до 10 промилле, что составляет 50% -ный порог либо 49,5%, либо 50,5% (в зависимости от того, используете ли вы ‹или‹ = compperator). В-третьих, увеличенный объем памяти (4 Кбайт с точностью до миллиметра). Вы можете уменьшить его, используя процентную точность или шкалу логарифмической точности.

Код генерации:

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();
}

Код поиска:

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
person DiddiZ    schedule 15.07.2012
comment
не могли бы вы поделиться где-нибудь полученным массивом? Может быть, кто-нибудь сможет найти достаточно хорошую формулу для его реальной кумулятивной функции распределения. - person Alnitak; 15.07.2012

Вот аналитическое решение, которое не зависит от хранения данных и является непрерывным. Следуя описанному здесь методу, я построил гистограмму значений шума Перлина, а затем аппроксимировал функцию непрерывного распределения суммируя гистограмму, поэтому cdf(x)

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

Затем я использовал Wolfram Alpha для аппроксимации cdf(x) полиномом пятой степени. Это дало мне эту функцию:

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

х ^ 5 + 0,00309887 х ^ 4-1,53841 х ^ 3-0,00343488 х ^ 2 + 1,29551 х + 0,500516 u = (u + 0,002591009999999949) / 1,0055419999999997; // пересечение (0,0) и (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

Теперь F(perlin.noise2(...)) приближается к равномерному распределению.

Эта функция не проходит через точки (-1,0) и (1,1), поэтому вы можете исправить ее как

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

Функция также отклоняется чуть выше 1 рядом с x = 1 и чуть ниже 0 рядом с x = -1, поэтому вы должны зафиксировать ее между 0 и 1, если это важно для вас.

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

(Я оставлю это довольно кратким, если не появится кто-то, кто хочет более подробной информации. Если да, оставьте комментарий.)

person Grumdrig    schedule 11.11.2014

Что ж, если он почти гауссовский, тогда 70% будет все ниже 0,75, см. Эту таблицу: http://www.roymech.co.uk/Useful_Tables/Statistics/Statistics_Normal_table.html

person Per Alexandersson    schedule 14.07.2012

Просто сложите 1 и разделите на 2? Это даст вам распределение с центром на 0,5 и от 0 до 1.

РЕДАКТИРОВАТЬ: вы хотите разделить порог 70% против 30%, вы должны использовать кумулятивную функцию распределения нормального law, вам просто нужно найти x, например, вероятность оказаться ниже x равна 0,7. Обратите внимание, что это работает только для нормального распределения, если ваше распределение всегда между -1 и 1, это не нормальное распределение. Интервал вывода нормального распределения должен составлять -∞ и + ∞. Решение, упомянутое @paxinum, могло бы быть проще, чем выполнение вычислений самостоятельно.

person AsTeR    schedule 14.07.2012
comment
Да, но он по-прежнему нормально распределяется, и я не знаю, сколько будет 70%. - person DiddiZ; 15.07.2012
comment
Нормальное распределение означает, что вы можете нормализовать его, вычитая среднее деление на стандартное отклонение. Затем таблица (например, та, что упомянута @Paxinum) дает вам порог, который следует учитывать (который составляет 0,53 для разделения на 70%). - person AsTeR; 19.07.2012