Bresenham'ın çizgi algoritmasını kayan nokta uç noktalarına nasıl genelleştirebilirim?


12

İki şeyi birleştirmeye çalışıyorum. Bir oyun yazıyorum ve kayan nokta uç noktaları ile bir çizgi üzerinde yatan ızgara karelerini belirlemem gerekiyor.

Çizgi çukur ızgarası

Üstelik dokunduğu tüm ızgara karelerini de içermesi gerekiyor (yani sadece Bresenham'ın çizgisi değil mavi olanı):

Bresenham vs tam süpürme

Birisi bana nasıl yapılacağına dair bir fikir verebilir mi? Açık çözüm, saf çizgi algoritması kullanmaktır, ancak daha optimize edilmiş (daha hızlı) bir şey var mı?


Bağlantının çevrimdışı olması durumunda, "raytracing için daha hızlı bir voksel geçiş algoritması" için google
Gustavo Maciel

Yanıtlar:


9

Bir ızgara geçiş algoritması arıyorsunuz. Bu makale iyi bir uygulama sunmaktadır;

Kağıt üzerinde bulunan 2D temel uygulama şöyledir:

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

Kağıt üzerinde bir 3D ışın döküm versiyonu da var.

Bağlantının çürümesi durumunda, adıyla birçok ayna bulabilirsiniz: Işın izleme için daha hızlı bir voksel geçiş algoritması .


Garip. Sanırım cevabı değiştireceğim ve ltjax'ı oylayacağım. Çünkü o makaleye olan bağlantınıza dayanarak çözdüm.
SmartK8

5

Blue'nun fikri iyi, ama uygulama biraz sakar. Aslında, bunu sqrt olmadan kolayca yapabilirsiniz. Diyelim ki dejenere vakaları ( BeginX==EndX || BeginY==EndY) hariç tuttunuz ve ilk çeyrekte yalnızca çizgi yönlerine odaklanalım BeginX < EndX && BeginY < EndY. En az bir başka çeyrek için de bir sürüm uygulamanız gerekir, ancak bu ilk çeyreğin sürümüne çok benzer - yalnızca diğer kenarları kontrol edersiniz. C'ish sözde kodunda:

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

Şimdi diğer kadranlar için, ++cxya ++cyda döngü koşulunu değiştirmeniz yeterlidir. Bunu çarpışma için kullanırsanız, muhtemelen 4 versiyonun hepsini de uygulamanız gerekir, aksi takdirde başlangıç ​​ve bitiş noktalarını uygun bir şekilde değiştirerek iki tanesinden kaçabilirsiniz.


Gustavo Maciel'in sağladığı algoritma biraz daha verimlidir. Sadece önce Ts'i belirler ve daha sonra dikey veya yatayya 1 ekler ve Ts'yi bir hücre boyutuna kaydırır. Ama bir cevaba dönüştürmediği için bunu en yakın cevap olarak kabul edeceğim.
SmartK8

3

Varsayımınız mutlaka hücreleri bulmak değil, bu ızgarada kesiştiği çizgileri bulmaktır.

Örneğin, resminizi çekerken hücreleri değil, geçtiği ızgaranın çizgilerini vurgulayabiliriz:

Kırmızı Hat

Bu daha sonra bir ızgara çizgisini geçerse, bu çizginin her iki tarafındaki hücrelerin dolu olanlar olduğunu gösterir.

Noktalarınızı piksellere ölçekleyerek kayan nokta çizginizin bunları geçip geçmeyeceğini bulmak için bir kavşak algoritması kullanabilirsiniz. 1.0: 1 kayan koordinat oranınız varsa: pikseller sıralanırsınız ve doğrudan çevirebilirsiniz. Çizgi segmenti kavşak algoritmasını kullanarak sol alt çizginizin (1,7) (2,7) çizginizle (1.3,6.2) (6.51,2.9) kesişip kesişmediğini kontrol edebilirsiniz. http://alienryderflex.com/intersect/

C'den C # 'a biraz çeviri yapılması gerekecek, ancak bu makaleden fikir edinebilirsiniz. Bağlantının kesilmesi durumunda aşağıdaki kodu koyacağım.

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

Yalnızca çizgi segmentlerinin ne zaman (ve nerede) kesiştiğini öğrenmeniz gerekiyorsa, işlevi aşağıdaki gibi değiştirebilirsiniz:

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

Merhaba, ızgara geçişi tam olarak tüm ızgara üzerinde binlerce hat kavşağını optimize etmek içindir. Bu binlerce hat kavşağı ile çözülemez. Bir oyunda oyuncunun geçemediği toprak çizgileri olan bir harita var. Bunlardan binlerce olabilir. Hangisinin pahalı kavşak için hesaplanacağını belirlemem gerekiyor. Bunları belirlemek için sadece oyuncu hareketindeki (veya ışık kaynağından gelen ışık) çizgilerin kesişimlerini hesaplamak istiyorum. Sizin durumunuzda her turda ~ 256x256x2 çizgi segmenti ile kesişimleri belirlemem gerekir. Bu hiç optimize edilmez.
SmartK8

Ama yine de cevap verdiğin için teşekkür ederim. Teknik olarak çalışır ve doğrudur. Ama benim için mümkün değil.
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 Demosu:

Imgur


1
Bu, kayan nokta sayısal hataları nedeniyle benim için başarısız oldu (döngü, bir sonraki tamsayı üzerinde en küçük kesir için ekstra bir yineleme yapacak ve bu da çizgi bitiş noktasını 'bitiş' konumunun ötesine itecektir). Basit düzeltme, ilk önce bir tavan olarak dist'i hesaplamaktır, böylece dx, dy, döngünün tam sayı yinelemelerine bölünür (bu, for döngüsünde tavanı (dist) kaybedebileceğiniz anlamına gelir).
PeteB

0

Bugün aynı problemle karşılaştım ve bir köstebek tepesinden oldukça büyük bir spagetti dağı yaptım ama işe yarayan bir şeyle sonuçlandım: https://github.com/SnpM/Pan-Line-Algorithm .

Benioku Dosyasından:

Bu algoritmanın temel konsepti, Bresenham'ınkine benzer, çünkü bir eksende 1 birim artar ve diğer eksende artışı test eder. Bununla birlikte, kesirler artmayı oldukça zorlaştırır ve çok sayıda pizza eklenmelidir. Örneğin, X = .21'den X = 1.21'e 5 eğimle artış, karmaşık bir sorun yaratır (bu kötü sayılar arasındaki koordinat modelleri tahmin edilmesi zor) ancak 5 eğimle 1'den 2'ye yükseltmek kolay bir sorun yaratır. Tamsayılar arasındaki koordinat deseninin çözülmesi çok kolaydır (sadece artan eksene dik bir çizgi). Kolay problemi elde etmek için artış, kesirli parça için ayrı ayrı yapılan tüm hesaplarla bir tamsayıya kaydırılır. Yani artışı artırmaya .21'de başlamak yerine,

BeniOku çözümü koddan çok daha iyi açıklar. Daha az baş ağrısına neden olacak şekilde revize etmeyi planlıyorum.

Bu soruya yaklaşık bir yıl geç kaldığımı biliyorum ama umarım bu, bu soruna bir çözüm arayan diğer kişilere de ulaşır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.