Почему эта шумовая функция не обрабатывает отрицательные аргументы? - PullRequest
0 голосов
/ 05 октября 2018

У меня есть код, адаптированный из «Улучшенный» шум Перлина

double improved_noise (double x, double y, double z)
{
    // Calculate the "unit cube" that the point asked will be located in
    // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
    // plus 1.  Next we calculate the location (from 0.0 to 1.0) in that
    // cube. We also fade the location to smooth the result.

    int xi = (int)x & 255;
    int yi = (int)y & 255;
    int zi = (int)z & 255;

    double xf = x - (int) x;
    double yf = y - (int) y;
    double zf = z - (int) z;

    double u = fade (xf);
    double v = fade (yf);
    double w = fade (zf);

    int aaa, aba, aab, abb, baa, bba, bab, bbb;
    auto & p = permutation;

    aaa = p[p[p[    xi ] +     yi ] +     zi ];
    aba = p[p[p[    xi ] + inc(yi)] +     zi ];
    aab = p[p[p[    xi ] +     yi ] + inc(zi)];
    abb = p[p[p[    xi ] + inc(yi)] + inc(zi)];
    baa = p[p[p[inc(xi)] +     yi ] +     zi ];
    bba = p[p[p[inc(xi)] + inc(yi)] +     zi ];
    bab = p[p[p[inc(xi)] +     yi ] + inc(zi)];
    bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];

    double x1, x2, y1, y2;

    // The gradient function calculates the dot product between a
    // pseudorandom gradient vector and the vector from the input
    // coordinate to the 8 surrounding points in its unit cube.

    // This is all then lerped together as a sort of weighted average
    // based on the faded (u,v,w) values we made earlier.

    x1 = lerp (
        grad (aaa, xf  , yf  , zf),
        grad (baa, xf-1, yf  , zf),
        u);

    x2 = lerp (
        grad (aba, xf  , yf-1, zf),
        grad (bba, xf-1, yf-1, zf),
        u);

    y1 = lerp (x1, x2, v);

    x1 = lerp (
        grad (aab, xf  , yf  , zf-1),
        grad (bab, xf-1, yf  , zf-1),
        u);

    x2 = lerp (
        grad (abb, xf  , yf-1, zf-1),
        grad (bbb, xf-1, yf-1, zf-1),
        u);

    y2 = lerp (x1, x2, v);

    return (lerp (y1, y2, w) + 1) / 2;
}

Я хотел периодический шум в одном направлении, поэтому я обернул это направление в круг в дополнительном измерении, назвав еговот так

 improved_noise (sin(x*2*M_PI), cos(x*2*M_PI), y))

Я получил странные результаты (большие и / или отрицательные).Некоторые эксперименты показали, что это происходит, когда аргументы improved_noise являются отрицательными.

Почему эта функция плохо обрабатывает отрицательные значения, и ее можно легко адаптировать, чтобы строка полного числа была допустимым аргументом?

1 Ответ

0 голосов
/ 05 октября 2018

improved_noise не был предназначен для обработки отрицательных входных данных.

Комментарий в нем гласит:

The left bound is ( |_x_|,|_y_|,|_z_| )…

Обозначение |…| предполагает, что абсолютное значение предназначено.Однако код вычисляет:

int xi = (int)x & 255;

В распространенных реализациях C (где используется дополнение до двух), это эффективно вычисляет остаток целочисленной части x по модулю 256. Например, если x - -3.25, его целочисленная часть равна -3, и это установит xi в 253 (что равно -3 + 256).

В этом есть две проблемы.Во-первых, 253 не является абсолютным значением -3, поэтому этот код не соответствует комментарию.Во-вторых, он берет «правую» границу единичного куба, содержащего точку (границу с большим значением), в то время как комментарии и поведение для положительных значений предполагают, что целью является установить xi, yiи zi до «левой» границы (той, которая имеет меньшее значение).

Далее, код устанавливает double xf = x - (int) x;.Для неотрицательных значений это дает дробную часть x.Например, если бы x было 3,25, xf было бы 0,25.Однако с отрицательными значениями и предшествующей & 255 операцией это сбивается с пути.Для x = -3,25 вычисляется -3,25 - 253 = -256,25.Но код, скорее всего, предназначен просто для интерполяции в единичном кубе, для дробных частей от 0 до 1. Какая бы функция ни использовалась для выполнения интерполяции, скорее всего, не поддерживается -256.25.

По сути, этот код никогда не разрабатывалсядля поддержки отрицательных значений, а исправление требует пересмотра его из первых принципов того, как он должен работать.

Исходный код , на который вы указываете , лучше:

int X = (int)Math.floor(x) & 255
…
x -= Math.floor(x);  

Первый правильно использует floor, чтобы найти «левую» границу, независимо от того, является ли x отрицательным или нет.Тогда это относится & 255 к этому.Предполагая, что два дополняют друг друга, это даст правильную координату в периодической мозаике.(Предполагая, что два дополнения не являются чисто переносимыми, и их следует документировать или избегать.)

Затем он правильно находит дробь, вычитая floor из x, а не вычитая результат & 255.Например, для x = -3,25 это даст целочисленную координату -4 и дробь 0,75.

Изменение improved_noise для аналогичной работы может помочь.Вы можете попробовать:

int xi = (int) floor(x) & 255;
int yi = (int) floor(y) & 255;
int zi = (int) floor(z) & 255;

double xf = x - floor(x);
double yf = y - floor(y);
double zf = z - floor(z);

Вы пометили этот вопрос как C ++, так и C. В C ++ предпочтительнее использовать std::floor вместо floor, и могут быть другие проблемы с различиями между C ++и С.

...