Як узагальнити лінійний алгоритм Брезенама до кінцевих точок з плаваючою комою?


12

Я намагаюся поєднати дві речі. Я пишу гру і мені потрібно визначити квадрати сітки, що лежать на лінії з кінцевими точками з плаваючою комою.

Лінія коритової сітки

Крім того, мені потрібно, щоб вона включала всі квадрати сітки, до яких вона торкається (тобто, не лише лінію Брезена, а синю):

Брезенхем проти повного зачитання

Чи може хтось запропонувати мені зрозуміти, як це зробити? Очевидним рішенням є використання алгоритму наївних ліній, але чи є щось більш оптимізоване (швидше)?


Якщо посилання переходить в офлайн, просто перейдіть на Google для "Більш швидкого алгоритму обходу вокселів для прослуховування"
Gustavo Maciel

Відповіді:


9

Ви шукаєте алгоритм обходу сітки. Цей документ дає хорошу реалізацію;

Ось основна реалізація в 2D, знайденій на папері:

loop {
    if(tMaxX < tMaxY) {
        tMaxX= tMaxX + tDeltaX;
        X= X + stepX;
    } else {
        tMaxY= tMaxY + tDeltaY;
        Y= Y + stepY;
    }
    NextVoxel(X,Y);
}

На папері також є версія 3D-випромінювання променів.

У випадку, якщо посилання гниє , ви можете знайти безліч дзеркал з його назвою: Більш швидкий алгоритм обходу вокселів для відстеження .


Ну, незручно. Я здогадуюсь, я переключу відповідь на вас і проголосуйте ltjax. Тому що я вирішив на основі вашої посилання на цей документ.
SmartK8

5

Ідея Блю хороша, але реалізація трохи незграбна. Насправді це можна легко зробити без sqrt. Припустимо на даний момент, що ви виключаєте вироджені випадки ( BeginX==EndX || BeginY==EndY) і зосереджуєтесь лише на напрямках ліній у першому квадранті, так BeginX < EndX && BeginY < EndY. Вам також доведеться впровадити версію принаймні для одного квадранта, але це дуже схоже на версію для першого квадранта - ви перевіряєте лише інші ребра. У псевдокоді C'ish:

int cx = floor(BeginX); // Begin/current cell coords
int cy = floor(BeginY);
int ex = floor(EndX); // End cell coords
int ey = floor(EndY);

// Delta or direction
double dx = EndX-BeginX;
double dy = EndY-BeginY;

while (cx < ex && cy < ey)
{
  // find intersection "time" in x dir
  float t0 = (ceil(BeginX)-BeginX)/dx;
  float t1 = (ceil(BeginY)-BeginY)/dy;

  visit_cell(cx, cy);

  if (t0 < t1) // cross x boundary first=?
  {
    ++cx;
    BeginX += t0*dx;
    BeginY += t0*dy;
  }
  else
  {
    ++cy;
    BeginX += t1*dx;
    BeginY += t1*dy;
  }
}

Тепер для інших квадрантів ви просто зміните умову ++cxабо ++cyі циклу. Якщо ви використовуєте це для зіткнення, вам, ймовірно, доведеться реалізувати всі 4 версії, інакше ви можете піти з двох, відповідним чином помінявши початкові та кінцеві точки.


Алгоритм, який надав Густаво Масель, трохи ефективніший. Він визначає лише спочатку Ts, а потім просто додає 1 до вертикалі чи горизонталі та зміщує Ts на розмір комірки. Але оскільки він не перетворив його на відповідь, я прийму цю як найближчу відповідь.
SmartK8

3

Ваше припущення не обов’язково знаходити комірки, але лінії, які вони перетинають на цій сітці.

Наприклад, взявши ваше зображення, ми можемо виділити не клітинки, а лінії сітки, яку він перетинає:

RedLines

Потім це показує, що якщо вона перетинає лінію сітки, то комірки з будь-якої сторони цієї лінії є тими, які заповнюються.

Ви можете використовувати алгоритм перетину, щоб дізнатись, чи буде перетинати їх лінія з плаваючою точкою шляхом масштабування точок до пікселів. Якщо у вас співвідношення плаваючих координат: пікселів у співвідношенні 1,0: 1, ви сортуєте їх та можете просто перекласти його безпосередньо. За допомогою алгоритму перетину відрізка лінії ви можете перевірити, чи перетинається нижня ліва лінія (1,7) (2,7) з вашою лінією (1.3,6.2) (6.51,2.9). http://alienryderflex.com/intersect/

Буде потрібен деякий переклад з c на C #, але ви можете отримати ідею з цього документа. Я поставлю код нижче, якщо випадок перерветься.

//  public domain function by Darel Rex Finley, 2006

//  Determines the intersection point of the line defined by points A and B with the
//  line defined by points C and D.
//
//  Returns YES if the intersection point was found, and stores that point in X,Y.
//  Returns NO if there is no determinable intersection point, in which case X,Y will
//  be unmodified.

bool lineIntersection(
double Ax, double Ay,
double Bx, double By,
double Cx, double Cy,
double Dx, double Dy,
double *X, double *Y) {

  double  distAB, theCos, theSin, newX, ABpos ;

  //  Fail if either line is undefined.
  if (Ax==Bx && Ay==By || Cx==Dx && Cy==Dy) return NO;

  //  (1) Translate the system so that point A is on the origin.
  Bx-=Ax; By-=Ay;
  Cx-=Ax; Cy-=Ay;
  Dx-=Ax; Dy-=Ay;

  //  Discover the length of segment A-B.
  distAB=sqrt(Bx*Bx+By*By);

  //  (2) Rotate the system so that point B is on the positive X axis.
  theCos=Bx/distAB;
  theSin=By/distAB;
  newX=Cx*theCos+Cy*theSin;
  Cy  =Cy*theCos-Cx*theSin; Cx=newX;
  newX=Dx*theCos+Dy*theSin;
  Dy  =Dy*theCos-Dx*theSin; Dx=newX;

  //  Fail if the lines are parallel.
  if (Cy==Dy) return NO;

  //  (3) Discover the position of the intersection point along line A-B.
  ABpos=Dx+(Cx-Dx)*Dy/(Dy-Cy);

  //  (4) Apply the discovered position to line A-B in the original coordinate system.
  *X=Ax+ABpos*theCos;
  *Y=Ay+ABpos*theSin;

  //  Success.
  return YES; }

Якщо вам потрібно дізнатися лише тоді, коли (і де) сегменти рядків перетинаються, ви можете змінити функцію наступним чином:

//  public domain function by Darel Rex Finley, 2006  

//  Determines the intersection point of the line segment defined by points A and B
//  with the line segment defined by points C and D.
//
//  Returns YES if the intersection point was found, and stores that point in X,Y.
//  Returns NO if there is no determinable intersection point, in which case X,Y will
//  be unmodified.

bool lineSegmentIntersection(
double Ax, double Ay,
double Bx, double By,
double Cx, double Cy,
double Dx, double Dy,
double *X, double *Y) {

  double  distAB, theCos, theSin, newX, ABpos ;

  //  Fail if either line segment is zero-length.
  if (Ax==Bx && Ay==By || Cx==Dx && Cy==Dy) return NO;

  //  Fail if the segments share an end-point.
  if (Ax==Cx && Ay==Cy || Bx==Cx && By==Cy
  ||  Ax==Dx && Ay==Dy || Bx==Dx && By==Dy) {
    return NO; }

  //  (1) Translate the system so that point A is on the origin.
  Bx-=Ax; By-=Ay;
  Cx-=Ax; Cy-=Ay;
  Dx-=Ax; Dy-=Ay;

  //  Discover the length of segment A-B.
  distAB=sqrt(Bx*Bx+By*By);

  //  (2) Rotate the system so that point B is on the positive X axis.
  theCos=Bx/distAB;
  theSin=By/distAB;
  newX=Cx*theCos+Cy*theSin;
  Cy  =Cy*theCos-Cx*theSin; Cx=newX;
  newX=Dx*theCos+Dy*theSin;
  Dy  =Dy*theCos-Dx*theSin; Dx=newX;

  //  Fail if segment C-D doesn't cross line A-B.
  if (Cy<0. && Dy<0. || Cy>=0. && Dy>=0.) return NO;

  //  (3) Discover the position of the intersection point along line A-B.
  ABpos=Dx+(Cx-Dx)*Dy/(Dy-Cy);

  //  Fail if segment C-D crosses line A-B outside of segment A-B.
  if (ABpos<0. || ABpos>distAB) return NO;

  //  (4) Apply the discovered position to line A-B in the original coordinate system.
  *X=Ax+ABpos*theCos;
  *Y=Ay+ABpos*theSin;

  //  Success.
  return YES; }

Привіт, обхід сітки саме для оптимізації тисяч перетину ліній по всій мережі. Це неможливо вирішити тисячами перетину ліній. У грі є карта з наземними лініями, які гравець не може перетнути. Їх може бути тисячі. Мені потрібно визначити, для кого слід обчислити дороге перехрестя. Для їх визначення я хочу лише обчислити перетини тих, хто знаходиться в русі гравця (або світла від джерела світла). У вашому випадку мені потрібно визначити перехрестя з ~ 256x256x2 рядковими сегментами кожного раунду. Це взагалі не було б оптимізовано.
SmartK8

Але все-таки дякую за вашу відповідь. Технічно це працює і правильно. Але просто для мене нездійсненне.
SmartK8

3
float difX = end.x - start.x;
float difY = end.y - start.y;
float dist = abs(difX) + abs(difY);

float dx = difX / dist;
float dy = difY / dist;

for (int i = 0, int x, int y; i <= ceil(dist); i++) {
    x = floor(start.x + dx * i);
    y = floor(start.y + dy * i);
    draw(x,y);
}
return true;

Демонстрація JS:

Імгур


1
Мені це не вдалося через числові помилки з плаваючою комою (цикл зробить додаткову ітерацію для найменшого дробу над наступним цілим числом, яке висуне кінцеву точку лінії за межі місця 'кінець'). Просте виправлення полягає в тому, щоб обчислити dist в якості перекриття в першу чергу, так що dx, dy діляться на ціле число ітерацій циклу (це означає, що ви можете втратити стелю (dist) у циклі for.
PeteB

0

Я зіткнувся з тією ж проблемою сьогодні і зробив досить велику гору спагетті з молистого пагорба, але закінчився тим, що працює: https://github.com/SnpM/Pan-Line-Algorithm .

Від ReadMe:

Основна концепція цього алгоритму подібна до Брезенхема тим, що він збільшується на 1 одиницю на одній осі і перевіряє збільшення на іншій осі. Фракції значно збільшують приріст, проте потрібно було додати чимало піц. Наприклад, збільшення від X = .21 до X = 1,21 з нахилом 5 становить складну проблему (координатні структури між цими противними числами важко передбачити), але приріст від 1 до 2 з нахилом у 5 створює просту проблему. Координатний малюнок між цілими числами вирішується дуже просто (лише лінія, перпендикулярна осі збільшення). Щоб отримати просту проблему, приріст зміщується на ціле число з усіма обчисленнями, зробленими окремо для дробової частини. Тож замість того, щоб починати приріст на .21,

Програма ReadMe пояснює рішення набагато краще, ніж код. Я планую переглянути, щоб це було менш головним болем.

Я знаю, що я запізнився на рік на це питання, але сподіваюся, що це потрапить до інших, хто шукає рішення цієї проблеми.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.