วิธีที่เร็วที่สุดในการหาจุดตัดขอบ 2D คืออะไร


62

สมมติว่าแต่ละกล่องวัตถุมีคุณสมบัติ x, y, ความกว้างความสูงและมีต้นกำเนิดที่จุดศูนย์กลางของพวกเขาและไม่ว่าวัตถุหรือกล่องขอบเขตการหมุน


กล่องเหล่านี้มีการจัดแนวแกนหรือวัตถุ
tenpn

3
เมื่อคุณถามคำถามนี้คุณจะต้องทดสอบทางแยกประเภทอื่น ๆ ในอนาคตอย่างแน่นอน;) ดังนั้นฉันขอแนะนำรายการเกี่ยวกับการแยกวัตถุ / วัตถุ ตารางแสดงจุดแยกระหว่างประเภทวัตถุยอดนิยมทั้งหมด (กล่องทรงกลมสามเหลี่ยมสามเหลี่ยมไซโคลเดอร์โคนกรวย ... ) ในสถานการณ์แบบคงที่หรือแบบไดนามิก
Dave O.

2
โปรดใช้ถ้อยคำของคุณใหม่เพื่อ จำกัด ขอบเขต rects จากมุมมองของฉันกล่องแสดงถึงวัตถุ 3 มิติ
Dave O.

คำตอบ:


55

(C-ish pseudocode - ปรับการเพิ่มประสิทธิภาพภาษาตามความเหมาะสม)

bool DoBoxesIntersect(Box a, Box b) {
  return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
         (abs(a.y - b.y) * 2 < (a.height + b.height));
}

ในภาษาอังกฤษ: ในแต่ละแกนให้ตรวจสอบว่าศูนย์กลางของกล่องนั้นอยู่ใกล้พอที่จะตัดกันหรือไม่ หากพวกเขาตัดกันบนแกนทั้งสองแล้วกล่องจะตัดกัน ถ้าพวกเขาทำไม่ได้พวกเขาก็ทำไม่ได้

คุณสามารถเปลี่ยน <'s เป็น <= ถ้าคุณต้องการนับสัมผัสขอบเป็นการตัดกัน หากคุณต้องการสูตรเฉพาะขอบสัมผัสคุณจะไม่สามารถใช้ == - ซึ่งจะบอกให้คุณรู้ว่ามุมสัมผัสหรือไม่หากสัมผัสกับขอบ return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b)คุณต้องการที่จะทำบางสิ่งบางอย่างมีเหตุผลเทียบเท่ากับ

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


9
นี่ถือว่าชัดเจนว่ากล่องอยู่ในแนวแกน
tenpn

1
Abs ไม่ควรช้าโดยเฉพาะ - ไม่ช้ากว่าเงื่อนไขอย่างน้อยและวิธีเดียวที่จะทำได้โดยไม่ต้อง abs (ที่ฉันรู้) เกี่ยวข้องกับเงื่อนไขพิเศษ
ZorbaTHut

4
ใช่มันถือว่าเป็นกล่องที่จัดเรียงตามแนวแกน โครงสร้างตามที่อธิบายไม่มีวิธีใดที่บ่งบอกถึงการหมุนดังนั้นฉันจึงรู้สึกว่าปลอดภัย
ZorbaTHut

3
ต่อไปนี้เป็นเคล็ดลับที่ดีสำหรับการเร่งความเร็วการคำนวณใน Actionscript (ส่วนใหญ่เป็นจำนวนเต็มคำนวณ): lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-mathฉันโพสต์สิ่งนี้เพราะมันมีความเร็วที่เร็วกว่า การแทนที่สำหรับ Math.abs () ซึ่งมีแนวโน้มที่จะทำให้สิ่งต่าง ๆ ช้าลงใน Actionscript แน่นอน
bummzack

2
คุณหายไปว่ามีต้นกำเนิดอยู่ตรงกลางไม่ใช่ที่ขอบซ้าย กล่องที่ทำงานจาก 0 ถึง 10 จะมี "x = 5" จริง ๆ ในขณะที่กล่องที่ทำงานจาก 8 ถึง 12 จะมี "x = 10" คุณท้ายด้วย=>abs(5 - 10) * 2 < (10 + 4) 10 < 14คุณจะต้องทำการปรับแต่งง่ายๆเพื่อให้สามารถใช้งานได้กับขนาดของมุมและขนาด
ZorbaTHut

37

ใช้งานได้สำหรับสี่เหลี่ยมสองรูปแบบที่จัดชิดกับแกน X และ Y
แต่ละสี่เหลี่ยมมีคุณสมบัติ:
"ซ้าย" พิกัด x ของด้านซ้าย
"บน" พิกัด y ของด้านบน
"ขวา" พิกัด x ของด้านขวา
"ล่าง" พิกัด y ของ ด้านล่างของมัน

function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
    return !(r2.left > r1.right
        || r2.right < r1.left
        || r2.top > r1.bottom
        || r2.bottom < r1.top);
}

โปรดทราบว่าสิ่งนี้ได้รับการออกแบบมาสำหรับระบบพิกัดซึ่งแกน + y ชี้ลงและแกน + x ถูกนำไปทางขวา (เช่นพิกัดหน้าจอ / พิกเซลทั่วไป) หากต้องการปรับให้เข้ากับระบบคาร์ทีเซียนทั่วไปที่ + y ชี้ขึ้นการเปรียบเทียบตามแกนแนวตั้งจะกลับด้านเช่น:

return !(r2.left > r1.right
    || r2.right < r1.left
    || r2.top < r1.bottom
    || r2.bottom > r1.top);

แนวคิดคือการจับภาพเงื่อนไขที่เป็นไปได้ทั้งหมดซึ่งรูปสี่เหลี่ยมผืนผ้าจะไม่ทับซ้อนกันจากนั้นคัดค้านคำตอบเพื่อดูว่ามีการทับซ้อนกันหรือไม่ โดยไม่คำนึงถึงทิศทางของแกนมันง่ายที่จะเห็นว่าสี่เหลี่ยมสองรูปนั้นจะไม่ทับซ้อนกันถ้า:

  • ขอบด้านซ้ายของ r2 นั้นอยู่ทางด้านขวามากกว่าขอบด้านขวาของ r1

     ________     ________
    |        |   |        |
    |   r1   |   |   r2   |
    |        |   |        |
    |________|   |________|
  • หรือขอบด้านขวาของ r2 นั้นอยู่ด้านซ้ายมากกว่าขอบด้านซ้ายของ r1

     ________     ________
    |        |   |        |
    |   r2   |   |   r1   |
    |        |   |        |
    |________|   |________|
  • หรือขอบบนของ r2 ต่ำกว่าขอบด้านล่างของ r1

     ________ 
    |        |
    |   r1   |
    |        |
    |________|
     ________ 
    |        |
    |   r2   |
    |        |
    |________|
  • หรือขอบด้านล่างของ r2 อยู่เหนือขอบด้านบนของ r1

     ________ 
    |        |
    |   r2   |
    |        |
    |________|
     ________ 
    |        |
    |   r1   |
    |        |
    |________|

ฟังก์ชั่นดั้งเดิม - และคำอธิบายทางเลือกว่าทำไมทำงาน - สามารถดูได้ที่นี่: http://tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect- แต่ละแบบอื่น ๆ หรือไม่ /


1
ใช้งานง่ายน่าแปลกใจและแสดงให้เห็นอีกครั้งว่าเมื่อค้นหาคำตอบยากเกินไปการพยายามหาคำตอบของคำถามตรงข้ามอาจช่วยคุณได้ ขอบคุณ!
Lodewijk

1
คุณควรระบุว่าแกน y ชี้ลง (ดังในภาพ) มิฉะนั้นความไม่เท่าเทียม r2.top> r1.bottom และ r2.bottom <r1.top จำเป็นต้องกลับด้าน
user1443778

@ user1443778 จับดี! ฉันไปข้างหน้าและอธิบายตรรกะที่อยู่เบื้องหลังอัลกอริทึมนี้อย่างหลวม ๆ ในระบบพิกัดที่ไม่เชื่อเรื่องพระเจ้าด้วยเช่นกัน
Ponkadoodle

11

ถ้าคุณต้องการกล่องขอบวัตถุให้ลองบทช่วยสอนนี้ในทฤษฎีการแยกแกนโดย metanet: http://www.metanetsoftware.com/technique/tutorialA.html

SAT ไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุด แต่ก็ค่อนข้างง่าย คุณพยายามที่จะหาบรรทัดเดียว (หรือเครื่องบินถ้า 3D) ที่จะแยกวัตถุของคุณ หากบรรทัดนี้มีอยู่มันรับประกันว่าจะต้องมีการ paralell ไปที่ขอบของหนึ่งในกล่องของคุณดังนั้นคุณจึงวนซ้ำผ่านการทดสอบขอบทั้งหมดเพื่อดูว่ามันแยกกล่อง

นอกจากนี้ยังใช้งานได้กับกล่องที่จัดแนวแกนโดย จำกัด เฉพาะแกน x / y


ฉันไม่ได้คิดถึงการหมุน แต่ขอบคุณที่เป็นลิงค์ที่น่าสนใจ
เลน

5

DoBoxesIntersect ด้านบนเป็นวิธีแก้ปัญหาที่ดี อย่างไรก็ตามหากคุณมีกล่องจำนวนมากคุณยังคงมีปัญหา O (N ^ 2) และคุณอาจพบว่าคุณต้องทำอะไรบางอย่างนอกเหนือจากที่ Kaj อ้างถึง (ในวรรณคดีการตรวจจับการชนกันแบบ 3 มิตินี่เป็นที่รู้จักกันว่ามีทั้งอัลกอริธึมกว้างและเฟสแคบเราจะทำบางสิ่งอย่างรวดเร็วเพื่อค้นหาการทับซ้อนที่เป็นไปได้ทั้งหมด คู่คือคู่แท้)

อัลกอริทึมแบบกว้างที่ฉันเคยใช้มาก่อนคือ "การกวาดและตัด"; สำหรับ 2D คุณจะต้องรักษารายการที่แยกไว้สองรายการของจุดเริ่มต้นและจุดสิ้นสุดของแต่ละกล่อง ตราบใดที่การเคลื่อนไหวของกล่องไม่ใช่ขนาดกล่องจากเฟรมหนึ่งไปหนึ่งเฟรมลำดับของรายการเหล่านี้จะไม่เปลี่ยนแปลงมากนักดังนั้นคุณสามารถใช้การเรียงลำดับฟองหรือการแทรกเพื่อรักษามันไว้ หนังสือ "การแสดงผลตามเวลาจริง" มีการเขียนที่ดีเกี่ยวกับการเพิ่มประสิทธิภาพที่คุณสามารถทำได้ แต่มันจะลดลงถึงเวลา O (N + K) ในระยะกว้างสำหรับกล่อง N, K ที่ทับซ้อนกันและมีโลกแห่งความเป็นเลิศที่ยอดเยี่ยม ประสิทธิภาพถ้าคุณสามารถจ่าย N ^ 2 booleans เพื่อติดตามว่ามีกล่องใดบ้างที่ตัดกันจากเฟรมหนึ่งไปอีกเฟรมหนึ่ง จากนั้นคุณจะมีเวลาโดยรวมของ O (N + K ^ 2) ซึ่งก็คือ << O (N ^ 2) ถ้าคุณมีกล่องจำนวนมาก แต่มีการทับซ้อนกันเพียงไม่กี่ตัว


5

คณิตศาสตร์จำนวนมากตรงนี้สำหรับปัญหาที่ง่ายมากสมมติว่าเรามี 4 คะแนนที่กำหนดไว้สำหรับ rect, top, left, bottom, right ...

ในกรณีของการพิจารณาว่า 2 rects ชนกันหรือไม่เราต้องการเพียงดูว่าสุดขั้วที่เป็นไปได้ทั้งหมดที่จะป้องกันการชนกันหากไม่พบสิ่งเหล่านี้ดังนั้น 2 rects ต้องชนกันถ้าคุณต้องการรวมการชนขอบเขตเพียงแค่แทนที่> และ < ด้วยความเหมาะสม> = และ = <

struct aRect{
  float top;
  float left;
  float bottom;
  float right;
};

bool rectCollision(rect a, rect b)
{
  return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}

ฉันไม่แน่ใจว่าทำไมถึงไม่เป็นคำตอบที่ได้รับคะแนนสูงสุด มันง่ายถูกต้องและมีประสิทธิภาพ
3

3

คำตอบของ ZorbaTHut รุ่นอื่น:

bool DoBoxesIntersect(Box a, Box b) {
     return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
     (abs(a.y - b.y) < (a.height + b.height) / 2);
}

ที่จริงแล้วเลขคณิตนั้นใช้ได้ทั้งสองทาง คุณสามารถทำการคำนวณทางคณิตศาสตร์ไปที่ด้านใดด้านหนึ่งของ <และมันไม่เปลี่ยน (การคูณด้วยค่าลบหมายความว่าคุณต้องเปลี่ยนน้อยกว่า) ในตัวอย่างนั้นกล่องไม่ควรชนกัน หากจุดกึ่งกลางของกล่อง A อยู่ที่ 1 มันจะขยายจาก -4 ถึง 6 กล่อง b ตรงกลางที่ 10 และครอบคลุม 7.5 ถึง 12.5 ไม่มีการชนกันที่นั่น ... ตอนนี้วิธีการที่ Wallacoloo โพสต์นั้นไม่ถูกต้องเท่านั้น แต่จะทำงานได้เร็วขึ้นในภาษาที่ใช้การลัดวงจรเนื่องจากการตรวจสอบส่วนใหญ่จะส่งคืนเท็จอยู่ดีการลัดวงจรในระยะสั้นสามารถตัดออกได้ในภายหลัง
Deleter

ใช่ฉันรู้เรื่องนี้เมื่อตื่นเช้านี้ คริสตะคอกฉันด้วยการผสมผสาน <> ของเขา
เลน

1
ปัญหาสองประการ: อันดับแรกการแบ่งมีแนวโน้มที่จะช้ากว่าการคูณอย่างมาก ประการที่สองหากค่าที่เกี่ยวข้องเป็นจำนวนเต็มสิ่งนี้อาจส่งผลให้เกิดปัญหาการตัดทอนจำนวนเต็ม (ax = 0, bx = 9, a.width = 9, b.width = 10: abs (0-9) <(9 + 10) / 2, 9 <19/2, 9 <9, ฟังก์ชันคืนค่าเท็จทั้งๆที่กล่องจะตัดกันอย่างชัดเจน)
ZorbaTHut

2

ขึ้นอยู่กับปัญหาที่คุณพยายามแก้ไขคุณอาจจะดีกว่าในการติดตามวัตถุของคุณในขณะที่คุณย้ายวัตถุเช่นเก็บรายการของตำแหน่ง x เริ่มต้นและสิ้นสุดและหนึ่งสำหรับตำแหน่งเริ่มต้นและสิ้นสุด y หากคุณต้องทำการตรวจสอบที่ทับซ้อนกันจำนวนมากดังนั้นจึงจำเป็นต้องปรับให้เหมาะสมคุณสามารถใช้สิ่งนี้เพื่อผลประโยชน์ของคุณเนื่องจากคุณสามารถค้นหาผู้ที่กำลังจะปิดทางด้านซ้ายของคุณได้ทันทีทุกคนที่ตอนจบอยู่ทางด้านซ้าย ทันที ใช้เหมือนกันสำหรับด้านบนด้านล่างและขวา
การทำบัญชีต้องใช้เวลาแน่นอนดังนั้นจึงเหมาะสำหรับสถานการณ์ที่มีวัตถุเคลื่อนที่น้อย แต่มีการตรวจสอบที่ทับซ้อนกันจำนวนมาก
อีกตัวเลือกหนึ่งคือ hashing เชิงพื้นที่ที่คุณฝากข้อมูลวัตถุตามตำแหน่งโดยประมาณ (ขนาดอาจใส่ em ในที่เก็บหลาย ๆ อัน) แต่มีอีกครั้งเฉพาะในกรณีที่มีวัตถุจำนวนมากโดยมีวัตถุค่อนข้างน้อยต่อการทำซ้ำเนื่องจากต้นทุนการทำบัญชี
โดยทั่วไปสิ่งใดที่หลีกเลี่ยง (n * n) / 2 (ถ้าคุณตรวจสอบวัตถุ a กับ b คุณจะไม่ต้องตรวจสอบ b กับชัด ๆ ) ช่วยมากกว่าการเพิ่มประสิทธิภาพการตรวจสอบกล่องขอบเขต หากการตรวจสอบขอบเขตกล่องเป็นปัญหาคอขวดฉันขอแนะนำอย่างจริงจังให้ค้นหาวิธีแก้ไขปัญหาอื่น ๆ


2

ระยะห่างระหว่างจุดศูนย์กลางไม่เท่ากับระยะห่างระหว่างมุม (เมื่อมีกล่องหนึ่งอยู่ในอีกมุมหนึ่ง) ดังนั้นโดยทั่วไปคำตอบนี้เป็นคำตอบที่ถูกต้อง (ฉันคิดว่า)

ระยะห่างระหว่างศูนย์ (สำหรับ, พูด, x): abs(x1+1/2*w1 - x2+1/2*w2)หรือ1/2 * abs(2*(x1-x2)+(w1-w2)

1/2 w1 + 1/2 w2 or 1/2 (w1+w2)ระยะต่ำสุดคือ ยกเลิกการแบ่งครึ่งดังนั้น ..

return 
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));

1
มีข้อความ "return" อยู่ตรงไหนบ้าง?
doppelgreener

1

นี่คือการดำเนินงานของฉันใน Java สมมติสถาปัตยกรรมเจ้าตัว-สมบูรณ์ หากคุณไม่ได้ใช้ twos-complement ให้ใช้การเรียกฟังก์ชันMath.absมาตรฐานแทน:

boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
    return
        (
            lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
            lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
            lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
        ) == 0;
}

int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
    final int
            leftWidth = leftMax - leftMin,
            rightWidth = rightMax - rightMin,

            leftMid = leftMin + ((leftMax - leftMin) >> 1),
            rightMid = rightMin + ((rightMax - rightMin) >> 1);

    return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}

int abs(int value) {
    final int mask = value >> (Integer.SIZE - 1);

    value ^= mask;
    value += mask & 1;
    return value;
}

สมมติว่าคอมไพเลอร์ / แอลแอลแอลดีเอ็มวีเอ็มครึ่งหนึ่งจะขยายฟังก์ชั่นเหล่านี้เพื่อหลีกเลี่ยงการเล่นปาหี่กองซ้อนและการค้นหา v-table สิ่งนี้จะล้มเหลวสำหรับค่าอินพุตที่อยู่ใกล้กับสุดขีด 32- บิต (เช่นInteger.MAX_VALUEและInteger.MIN_VALUE)


0

วิธีที่เร็วที่สุดคือการรวมค่าทั้ง 4 ไว้ในการลงทะเบียนเวคเตอร์เดียว

[ min.x, min.y, -max.x, -max.y ]เก็บกล่องในเวกเตอร์ที่มีค่าดังต่อไปนี้ หากคุณเก็บกล่องแบบนี้การทดสอบจุดตัดจะทำตาม 3 คำสั่ง CPU เท่านั้น:

_mm_shuffle_ps เพื่อจัดลำดับกล่องที่สองอีกครั้งให้ปัด min และ max halves

_mm_xor_psพร้อมหมายเลขมายากล_mm_set1_ps(-0.0f)เพื่อพลิกสัญญาณของค่าทั้ง 4 ในกล่องที่สอง

_mm_cmple_ps เพื่อเปรียบเทียบค่าทั้งหมด 4 ค่าด้วยกันโดยเปรียบเทียบการลงทะเบียนสองรายการต่อไปนี้:

[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]

ในที่สุดหากจำเป็น_mm_movemask_psเพื่อให้ได้ผลลัพธ์จากหน่วยเวกเตอร์เป็นสเกลาร์รีจิสเตอร์ ค่า 0 หมายถึงกล่องที่ตัดกัน หรือถ้าคุณมีมากกว่า 2 ช่องสิ่งนี้ไม่จำเป็นให้ทิ้งค่าไว้ในการลงทะเบียนแบบเวกเตอร์และใช้การดำเนินการระดับบิตเพื่อรวมผลลัพธ์จากหลายกล่อง

คุณไม่ได้ระบุภาษาหรือแพลตฟอร์ม แต่การรองรับ SIMD เช่นนี้หรือคล้ายกันมากมีให้บริการในทุกแพลตฟอร์มและทุกภาษา บนมือถือ ARM มี NEON SIMD พร้อมสิ่งที่คล้ายกันมาก .NET มี Vector128 ใน System.Runtime.Intrinsics namespace และอื่น ๆ

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