Wie verallgemeinere ich Bresenhams Linienalgorithmus auf Gleitkomma-Endpunkte?


12

Ich versuche zwei Dinge zu kombinieren. Ich schreibe ein Spiel und muss die Gitterquadrate bestimmen, die auf einer Linie mit den Gleitkomma-Endpunkten liegen.

Linie durch Gitter

Außerdem muss es alle Gitterquadrate enthalten, die es berührt (dh nicht nur Bresenhams Linie, sondern die blaue):

Bresenham vs volle fegen

Kann mir jemand einen Einblick geben, wie das geht? Die naheliegende Lösung ist die Verwendung eines naiven Linienalgorithmus. Gibt es jedoch etwas Optimierteres (schnelleres)?


Antworten:


9

Sie suchen nach einem Gitterdurchquerungsalgorithmus. Dieses Papier gibt eine gute Umsetzung;

Hier ist die grundlegende Implementierung in 2D, die auf dem Papier zu finden ist:

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

Es gibt auch eine 3D-Raycasting-Version auf dem Papier.

Falls die Verbindung rotiert , finden Sie viele Spiegel mit dem Namen: Ein schnellerer Voxel-Durchquerungsalgorithmus für das Raytracing .


Nun, ungeschickt. Ich schätze, ich schalte die Antwort auf dich um und stimme für ltjax. Weil ich auf der Grundlage Ihres Links zu diesem Artikel gelöst habe.
SmartK8

5

Die Idee von Blue ist gut, aber die Implementierung ist etwas umständlich. In der Tat können Sie es leicht ohne sqrt tun. Nehmen wir für den Moment an, dass Sie entartete Fälle ausschließen (BeginX==EndX || BeginY==EndY ) ausschließen und sich daher nur auf die Linienrichtungen im ersten Quadranten konzentrieren BeginX < EndX && BeginY < EndY. Sie müssen auch eine Version für mindestens einen anderen Quadranten implementieren, aber das ist der Version für den ersten Quadranten sehr ähnlich - Sie überprüfen nur andere Kanten. Im C'ish-Pseudocode:

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

Jetzt ändern Sie für andere Quadranten einfach die Bedingung ++cxoder ++cyund die Schleifenbedingung. Wenn Sie dies für eine Kollision verwenden, müssen Sie wahrscheinlich alle 4 Versionen implementieren, andernfalls können Sie mit zwei davonkommen, indem Sie die Anfangs- und Endpunkte entsprechend vertauschen.


Der von Gustavo Maciel bereitgestellte Algorithmus ist etwas effizienter. Es bestimmt nur zuerst Ts und addiert dann nur 1 zu vertikal oder horizontal und verschiebt Ts um eine Zellengröße. Aber da er es nicht in eine Antwort umgewandelt hat, akzeptiere ich diese als die nächste Antwort.
SmartK8

3

Es ist nicht unbedingt Ihre Annahme, die Zellen zu finden, sondern die Linien, die sie in diesem Raster kreuzen.

Wenn Sie beispielsweise ein Bild aufnehmen, können wir nicht die Zellen, sondern die Linien des Gitters hervorheben, das es kreuzt:

Rote Linien

Dies zeigt dann, dass, wenn es eine Gitterlinie kreuzt, die Zellen auf beiden Seiten dieser Linie diejenigen sind, die gefüllt sind.

Mithilfe eines Schnittalgorithmus können Sie ermitteln, ob Ihre Gleitkomma-Linie diese schneidet, indem Sie Ihre Punkte in Pixel skalieren. Wenn Sie ein Verhältnis von fließenden Koordinaten zu Pixeln von 1,0: 1 haben, werden Sie sortiert und können es einfach direkt übersetzen. Mit dem Liniensegment-Schnittalgorithmus können Sie prüfen, ob sich Ihre untere linke Linie (1,7) (2,7) mit Ihrer Linie (1.3,6.2) (6.51,2.9) schneidet.http://alienryderflex.com/intersect/

Einige Übersetzungen von c nach C # werden benötigt, aber Sie können die Idee aus diesem Papier ableiten. Ich werde den Code unten setzen, falls der Link bricht.

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

Wenn Sie nur herausfinden müssen, wann (und wo) sich die Liniensegmente schneiden, können Sie die Funktion wie folgt ändern:

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

Hallo, die Gitterdurchquerung dient genau dem Zweck, Tausende von Linienschnittpunkten im gesamten Gitter zu optimieren. Dies kann nicht durch Tausende von Linienkreuzungen gelöst werden. Ich habe eine Karte in einem Spiel mit Grundlinien, die der Spieler nicht überqueren kann. Es kann Tausende davon geben. Ich muss bestimmen, für welche die teure Kreuzung berechnet werden soll. Um diese zu bestimmen, möchte ich nur die Schnittpunkte der Linien der Spielerbewegung (oder des Lichts von der Lichtquelle) berechnen. In Ihrem Fall müsste ich Schnittpunkte mit ~ 256x256x2 Liniensegmenten pro Runde bestimmen. Das wäre überhaupt nicht optimiert.
SmartK8

Aber trotzdem danke für deine Antwort. Technisch funktioniert es und ist korrekt. Aber für mich einfach nicht machbar.
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 Demo:

Imgur


1
Dies ist für mich aufgrund von numerischen Gleitkommafehlern fehlgeschlagen (die Schleife führt eine zusätzliche Iteration für den kleinsten Bruch über die nächste Ganzzahl durch, wodurch der Linienendpunkt über die Endposition hinaus verschoben wird). Die einfache Lösung besteht darin, dist zunächst als Ceil zu berechnen, sodass dx, dy durch die ganzzahlige Anzahl der Iterationen der Schleife dividiert werden (dies bedeutet, dass Sie den Ceil (dist) in der for-Schleife verlieren können).
PeteB

0

Ich bin heute auf dasselbe Problem gestoßen und habe aus einem Maulwurfshügel einen ziemlich großen Berg Spaghetti gemacht. Am Ende hatte ich jedoch etwas, das funktioniert: https://github.com/SnpM/Pan-Line-Algorithm .

Aus der Liesmich:

Das Kernkonzept dieses Algorithmus ähnelt dem von Bresenham, indem er auf einer Achse um 1 Einheit inkrementiert und die Zunahme auf der anderen Achse testet. Brüche erschweren jedoch das Inkrementieren erheblich, und es mussten viele Pizzas hinzugefügt werden. Beispielsweise ist das Inkrementieren von X = 0,21 auf X = 1,21 mit einer Steigung von 5 ein komplexes Problem (Koordinatenmuster zwischen diesen bösen Zahlen) sind schwer vorherzusagen), aber ein Inkrementieren von 1 auf 2 mit einer Steigung von 5 ist ein leichtes Problem. Das Koordinatenmuster zwischen ganzen Zahlen ist sehr einfach zu lösen (nur eine Linie senkrecht zur inkrementierenden Achse). Um das Problem zu lösen, wird das Inkrementieren auf eine ganze Zahl verschoben, wobei alle Berechnungen für das Bruchstück separat durchgeführt werden. Anstatt also das Inkrementieren auf .21 zu starten,

Die Liesmich erklärt die Lösung viel besser als der Code. Ich plane, es zu überarbeiten, um weniger Kopfschmerzen zu verursachen.

Ich weiß, dass ich ungefähr ein Jahr zu spät für diese Frage bin, aber ich hoffe, dass dies auch andere erreicht, die nach einer Lösung für dieses Problem suchen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.