Есть ли более эффективный способ текстурирования круга? - PullRequest
0 голосов
/ 01 апреля 2020

Я пытаюсь создать случайно сгенерированный "pl anet" (круг), и я хочу, чтобы области воды, земли и листвы определялись шумом перлина или чем-то подобным. В настоящее время у меня есть этот (псевдо) код:

for (int radius = 0; radius < circleRadius; radius++) {
    for (float theta = 0; theta < TWO_PI; theta += 0.1) {
        float x = radius * cosine(theta);
        float y = radius * sine(theta);
        int colour = whateverFunctionIMake(x, y);
        setPixel(x, y, colour);
    }
}

Мало того, что это не работает (есть «промежутки» в круге из-за проблем с точностью), это невероятно медленно. Даже если я увеличу разрешение путем изменения приращения до 0,01, у него все еще будут отсутствующие пиксели и он будет еще медленнее (я получаю 10 кадров в секунду на своем посредственном компьютере, используя Java (я знаю, что не самый лучший) и с шагом 0,01. Это, безусловно, неприемлемо для игры).

Как я могу достичь аналогичного результата, будучи гораздо менее затратным в вычислительном отношении?

Заранее спасибо.

1 Ответ

0 голосов
/ 08 апреля 2020

Почему бы не использовать:

(x-x0)^2 + (y-y0)^2 <= r^2

так просто:

int x0=?,y0=?,r=?; // your planet position and size
int x,y,xx,rr,col;
for (rr=r*r,x=-r;x<=r;x++)
 for (xx=x*x,y=-r;y<=r;y++)
  if (xx+(y*y)<=rr)
   {
   col = whateverFunctionIMake(x, y);
   setPixel(x0+x, y0+y, col);   
   }

все на целых числах, без плавающих или медленных операций, без пробелов ... Не забудьте использовать randseed для функция раскраски ...

[Edit1] еще кое-что

Теперь, если вам нужна скорость, вам нужен прямой пиксельный доступ (на большинстве платформ Pixels, SetPixel, PutPixels et c являются slooow, потому что они выполняют много таких вещей, как проверка диапазона, преобразование цветов и т. д. c ...) В случае, если вы получили прямой пиксельный доступ или визуализировали в свой собственный массив / изображение все, что вам нужно, чтобы добавить отсечение с помощью экран (поэтому вам не нужно проверять, находится ли пиксель внутри экрана на каждом пикселе), чтобы избежать нарушений доступа, если ваш круг перекрывает экран.

Как уже упоминалось в комментариях, вы можете избавиться от x*x и y*y внутри l oop с использованием предыдущего значения (поскольку оба x,y только увеличиваются). Для получения дополнительной информации об этом см .:

математика выглядит так:

(x+1)^2 = (x+1)*(x+1) = x^2 + 2x + 1

поэтому вместо xx = x*x мы просто делаем xx+=x+x+1 для еще не увеличенного x или xx+=x+x-1, если x уже увеличено.

Когда все сложено, я получил это:

void circle(int x,int y,int r,DWORD c)
    {
    // my Pixel access
    int **Pixels=Main->pyx;         // Pixels[y][x]
    int   xs=Main->xs;              // resolution
    int   ys=Main->ys;
    // circle
    int sx,sy,sx0,sx1,sy0,sy1;      // [screen]
    int cx,cy,cx0,    cy0    ;      // [circle]
    int rr=r*r,cxx,cyy,cxx0,cyy0;   // [circle^2]
    // BBOX + screen clip
    sx0=x-r; if (sx0>=xs) return; if (sx0<  0) sx0=0;
    sy0=y-r; if (sy0>=ys) return; if (sy0<  0) sy0=0;
    sx1=x+r; if (sx1<  0) return; if (sx1>=xs) sx1=xs-1;
    sy1=y+r; if (sy1<  0) return; if (sy1>=ys) sy1=ys-1;
    cx0=sx0-x; cxx0=cx0*cx0;
    cy0=sy0-y; cyy0=cy0*cy0;
    // render
    for (cxx=cxx0,cx=cx0,sx=sx0;sx<=sx1;sx++,cxx+=cx,cx++,cxx+=cx)
     for (cyy=cyy0,cy=cy0,sy=sy0;sy<=sy1;sy++,cyy+=cy,cy++,cyy+=cy)
      if (cxx+cyy<=rr)
       Pixels[sy][sx]=c;
    }

При этом отображается круг с радиусом 512 px в ~35ms, поэтому 23.5 Mpx/s заполняется при настройке шахты (64-битное однопоточное VCL / GDI 32-битное приложение AMD A8-5500 Win7, закодированное BDS2006 C ++). Просто измените прямой доступ к пикселям на стиль / API, который вы используете ...

[Edit2]

, чтобы измерить скорость на x86 / x64, вы можете использовать RDTSC asm Здесь приведен некоторый древний код C ++, который я использовал в возрасте go (в 32-битной среде без встроенных 64-битных компонентов):

double _rdtsc()
    {
    LARGE_INTEGER x; // unsigned 64bit integer variable from windows.h I think
    DWORD l,h;       // standard unsigned 32 bit variables
    asm {
        rdtsc
        mov l,eax
        mov h,edx
        }
    x.LowPart=l;
    x.HighPart=h;
    return double(x.QuadPart);
    }

Возвращает часы, прошедшие с момента включения вашего ЦП. Помните, что вы должны учитывать переполнение, поскольку на быстрых машинах 32-битный счетчик переполняется за секунды. Также каждое ядро ​​имеет отдельный счетчик, так что установите сродство к одному процессору. На часах с переменной частотой перед измерением нагрейте процессор с помощью некоторых вычислений, а для преобразования во время просто поделите его на тактовую частоту процессора. Чтобы получить его, просто сделайте следующее:

t0=_rdtsc()
sleep(250);
t1=_rdtsc();
fcpu = (t1-t0)*4;

и измерение:

t0=_rdtsc()
mesured stuff
t1=_rdtsc();
time = (t1-t0)/fcpu

, если t1<t0 переполнено, и вам нужно добавить постоянную для результата или измерения снова. Также измеряемый процесс должен занимать меньше, чем период переполнения. Для повышения точности игнорируйте детализацию ОС. для получения дополнительной информации см .:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...