ฉันจะพูดคุยเกี่ยวกับอัลกอริธึมบรรทัดของ Bresenham ไปยังจุดลอยตัวได้อย่างไร


12

ฉันพยายามรวมสองสิ่งเข้าด้วยกัน ฉันกำลังเขียนเกมและฉันจำเป็นต้องกำหนดสี่เหลี่ยมกริดที่วางอยู่บนเส้นตรงกับจุดสิ้นสุดของจุดลอยตัว

เส้นตารางราง

ยิ่งกว่านั้นฉันต้องการมันเพื่อรวมสี่เหลี่ยมกริดทั้งหมดที่มันแตะ (เช่นไม่ใช่แค่เส้นของ Bresenhamแต่เป็นสีน้ำเงิน):

Bresenham vs กวาดเต็ม

มีใครให้ข้อมูลเชิงลึกเกี่ยวกับวิธีการใช้หรือไม่ วิธีแก้ปัญหาที่ชัดเจนคือการใช้อัลกอริธึมแบบไร้เดียงสา แต่มีบางสิ่งที่ได้รับการปรับให้เหมาะสมที่สุด (เร็วกว่า)?


ในกรณีที่การเชื่อมโยงออฟไลน์เพียง google สำหรับ "อัลกอริทึม voxel traversal ที่เร็วขึ้นสำหรับ raytracing"
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 บนกระดาษ

ในกรณีที่การเชื่อมโยงเน่าคุณสามารถหากระจกจำนวนมากที่มีชื่อของมัน: ที่รวดเร็ว voxel ข้ามขั้นตอนวิธีสำหรับ raytracing


ดีที่น่าอึดอัดใจ ฉันเดาว่าฉันจะเปลี่ยนคำตอบให้คุณและลงคะแนน ltjax เพราะฉันแก้ไขตามลิงก์ของคุณไปยังกระดาษนั้น
SmartK8

5

แนวคิดของ Blue นั้นดี แต่การใช้งานนั้นค่อนข้างงุ่มง่าม ในความเป็นจริงคุณสามารถทำได้โดยไม่ต้อง sqrt สมมติว่าในขณะที่คุณแยกกรณีที่เลวลง ( BeginX==EndX || BeginY==EndY) และมุ่งเน้นไปที่ทิศทางของเส้นในจตุภาคแรกBeginX < EndX && BeginY < EndYเท่านั้น คุณจะต้องใช้เวอร์ชันสำหรับ Quadrant อื่นอย่างน้อยหนึ่งด้วย แต่นั่นก็คล้ายกับเวอร์ชันสำหรับ Quadrant แรก - คุณตรวจสอบขอบอื่น ๆ เท่านั้น ในรหัสหลอก 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;
  }
}

ตอนนี้สำหรับ Quadrants อื่น ๆ คุณแค่เปลี่ยน++cxหรือหรือ++cyและเงื่อนไขลูป หากคุณใช้สิ่งนี้ในการชนคุณอาจต้องติดตั้งทั้ง 4 เวอร์ชันมิฉะนั้นคุณสามารถใช้สองรุ่นนี้ได้โดยสลับจุดเริ่มต้นและจุดสิ้นสุดอย่างเหมาะสม


อัลกอริทึม Gustavo Maciel ให้เป็นบิตที่มีประสิทธิภาพมากขึ้น มันเป็นตัวกำหนด 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:

Imgur


1
สิ่งนี้ล้มเหลวสำหรับฉันเนื่องจากข้อผิดพลาดของตัวเลขทศนิยม (ลูปจะทำซ้ำพิเศษสำหรับเศษส่วนเล็กที่สุดในจำนวนเต็มถัดไปซึ่งจะผลักดันจุดสิ้นสุดของบรรทัดเกินตำแหน่ง 'สิ้นสุด') การแก้ไขอย่างง่ายคือการคำนวณ dist เป็น ceil ในลำดับแรกดังนั้น dx, dy จะถูกหารด้วยจำนวนเต็มของการวนซ้ำของลูป (ซึ่งหมายความว่าคุณสามารถสูญเสีย ceil (dist) ใน for loop)
PeteB

0

ฉันวิ่งเข้าไปในปัญหาเดียวกันในวันนี้และทำภูเขาใหญ่สวยของปาเก็ตตี้ออกมาจากเนินเขาตุ่น แต่จบลงด้วยกับสิ่งที่งาน: https://github.com/SnpM/Pan-Line-Algorithm

จาก ReadMe:

แนวคิดหลักของอัลกอริทึมนี้คล้ายกับของ Bresenham ซึ่งจะเพิ่มขึ้นทีละ 1 ยูนิตในหนึ่งแกนและทดสอบการเพิ่มขึ้นของแกนอื่น ๆ เศษส่วนทำให้การเพิ่มขึ้นยากขึ้นมากและต้องเพิ่มพิซซ่าจำนวนมากตัวอย่างเช่นการเพิ่มจาก X = .21 เป็น X = 1.21 ด้วยความชัน 5 ทำให้เกิดปัญหาซับซ้อน (ประสานงานระหว่างตัวเลขที่น่ารังเกียจเหล่านั้น ยากที่จะคาดการณ์) แต่การเพิ่มขึ้นจาก 1 เป็น 2 โดยมีความชัน 5 ทำให้เป็นปัญหาง่าย รูปแบบการประสานระหว่างจำนวนเต็มนั้นง่ายต่อการแก้ไข (เพียงแค่เส้นตั้งฉากกับแกนที่เพิ่มขึ้น) เพื่อให้ได้ปัญหาง่าย ๆ การเพิ่มจะถูกชดเชยด้วยจำนวนเต็มโดยการคำนวณทั้งหมดแยกออกเป็นเศษส่วน ดังนั้นแทนที่จะเริ่มต้นการเพิ่มขึ้นของ. 21

ReadMe อธิบายวิธีแก้ปัญหาได้ดีกว่ารหัสมาก ฉันวางแผนที่จะแก้ไขเพื่อให้ปวดหัวน้อยลง

ฉันรู้ว่าฉันมีคำถามนี้มาประมาณหนึ่งปีแล้ว แต่ฉันหวังว่าสิ่งนี้จะช่วยให้ผู้อื่นที่กำลังมองหาวิธีแก้ไขปัญหานี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.