У меня есть код, адаптированный из «Улучшенный» шум Перлина
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
отрицательны.
Почему эта функция плохо обрабатывает отрицательные значения, и ее легко адаптировать, чтобы строка с полным числом была допустимым аргументом?
improved_noise
не был разработан для обработки отрицательных входных данных.
Комментарий в нем говорит:
Граница слева (| _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 ++ и C.
Других решений пока нет …