Come generalizzare l'algoritmo di linea di Bresenham agli endpoint a virgola mobile?


12

Sto cercando di combinare due cose. Sto scrivendo una partita e devo determinare i quadrati della griglia che giacciono su una linea con gli endpoint a virgola mobile.

Griglia della linea

Inoltre ne ho bisogno per includere tutti i quadrati della griglia che tocca (cioè non solo la linea di Bresenham ma quella blu):

Bresenham vs full sweep

Qualcuno può offrirmi qualche idea su come farlo? La soluzione ovvia è usare l'algoritmo di linea ingenua, ma c'è qualcosa di più ottimizzato (più veloce)?


Nel caso in cui il collegamento non sia in linea, basta cercare "Un algoritmo di attraversamento voxel più veloce per il raytracing"
Gustavo Maciel,

Risposte:


9

Stai cercando un algoritmo di attraversamento della griglia. Questo documento fornisce una buona attuazione;

Ecco l'implementazione di base in 2D trovata sul documento:

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

C'è anche una versione di ray-casting 3D sulla carta.

Nel caso in cui il collegamento marcisca , puoi trovare molti mirror con il suo nome: un algoritmo di attraversamento del voxel più veloce per il raytracing .


Bene, imbarazzante. Immagino che cambierò risposta a te e voterò ltjax. Perché ho risolto in base al tuo link a quel documento.
SmartK8

5

L'idea di Blue è buona, ma l'implementazione è un po 'goffa. In effetti, puoi farlo facilmente senza sqrt. Supponiamo per il momento che si escludano i casi degeneri ( BeginX==EndX || BeginY==EndY) e ci si concentri solo sulle direzioni delle linee nel primo quadrante, quindi BeginX < EndX && BeginY < EndY. Dovrai implementare una versione anche per almeno un altro quadrante, ma è molto simile alla versione per il primo quadrante: controlli solo gli altri bordi. Nello pseudo codice 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;
  }
}

Ora, per altri quadranti, basta cambiare la ++cxo ++cye la condizione del ciclo. Se lo usi per la collisione, probabilmente devi implementare tutte e 4 le versioni, altrimenti puoi farne due scambiando opportunamente i punti di inizio e fine.


L'algoritmo fornito da Gustavo Maciel è un po 'più efficiente. Determina solo il primo Ts e poi aggiunge solo 1 a verticale o orizzontale e sposta Ts di una dimensione di cella. Ma poiché non l'ha convertito in una risposta, accetterò questa come la risposta più vicina.
SmartK8

3

Il tuo presupposto non è necessariamente quello di trovare le celle ma le linee che attraversa su questa griglia.

Ad esempio prendendo la tua immagine possiamo evidenziare non le celle, ma le linee della griglia che attraversa:

Linee rosse

Ciò mostra quindi che se attraversa una linea della griglia, le celle ai lati di questa linea sono quelle che vengono riempite.

È possibile utilizzare un algoritmo di intersezione per scoprire se la linea di virgola mobile li attraverserà ridimensionando i punti in pixel. Se hai un rapporto 1,0: 1 di coordinate mobili: pixel, allora sei ordinato e puoi semplicemente tradurlo direttamente. Utilizzando l'algoritmo di intersezione del segmento Linea è possibile verificare se la linea in basso a sinistra (1,7) (2,7) si interseca con la linea (1.3,6.2) (6.51,2.9). http://alienryderflex.com/intersect/

Sarà necessaria una traduzione da C a C # ma è possibile ottenere l'idea da quel documento. Metterò il codice qui sotto nel caso in cui il collegamento si interrompa.

//  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; }

Se devi scoprire solo quando (e dove) i segmenti di linea si intersecano, puoi modificare la funzione come segue:

//  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; }

Ciao, l'attraversamento della griglia è esattamente allo scopo di ottimizzare migliaia di intersezioni di linee su tutta la griglia. Questo non può essere risolto da migliaia di intersezioni di linee. Ho una mappa in un gioco con linee di terra che il giocatore non può attraversare. Ce ne possono essere migliaia. Devo determinare per quale calcolare l'incrocio costoso. Per determinarli voglio solo calcolare le intersezioni di quelle in linea con il movimento del giocatore (o la luce dalla sorgente luminosa). Nel tuo caso avrei bisogno di determinare le intersezioni con ~ 256x256x2 segmenti di linea ogni giro. Non sarebbe affatto ottimizzato.
SmartK8

Ma ancora grazie per la tua risposta. Tecnicamente funziona ed è corretto. Ma non è fattibile per me.
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;

Demo JS:

Imgur


1
Questo non è riuscito per me a causa di errori numerici in virgola mobile (il loop eseguirà un'iterazione extra per la frazione più piccola sul numero intero successivo che spingerà il punto finale della linea oltre la posizione 'end'). La semplice soluzione è di calcolare dist come un ceil in primo luogo, quindi dx, dy sono divisi per il numero intero di iterazioni del ciclo (questo significa che puoi perdere il ceil (dist) nel ciclo for).
PeteB,

0

Ho incontrato lo stesso problema oggi e ho fatto una montagna piuttosto grande di spaghetti da una collina della talpa ma ho finito con qualcosa che funziona: https://github.com/SnpM/Pan-Line-Algorithm .

Dal file Leggimi:

Il concetto centrale di questo algoritmo è simile a quello di Bresenham in quanto incrementa di 1 unità su un asse e verifica l'aumento sull'altro asse. Le frazioni rendono l'incremento considerevolmente più difficile, tuttavia, e molte pizze dovevano essere aggiunte. Ad esempio, l'incremento da X = .21 a X = 1.21 con una pendenza di 5 crea un problema complesso (schemi di coordinate tra quei numeri cattivi sono difficili da prevedere) ma l'incremento da 1 a 2 con una pendenza di 5 rende facile il problema. Il modello di coordinate tra numeri interi è molto semplice da risolvere (solo una linea perpendicolare all'asse incrementale). Per ottenere il problema più semplice, l'incremento viene spostato su un numero intero con tutti i calcoli eseguiti separatamente per il pezzo frazionario. Quindi, invece di iniziare l'incremento su .21,

Il file Leggimi spiega la soluzione molto meglio del codice. Sto programmando di rivederlo per essere meno inducente al mal di testa.

So di essere in ritardo di circa un anno a questa domanda, ma spero che questo arrivi agli altri che cercano una soluzione a questo problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.