สมมติว่าแต่ละกล่องวัตถุมีคุณสมบัติ x, y, ความกว้างความสูงและมีต้นกำเนิดที่จุดศูนย์กลางของพวกเขาและไม่ว่าวัตถุหรือกล่องขอบเขตการหมุน
สมมติว่าแต่ละกล่องวัตถุมีคุณสมบัติ x, y, ความกว้างความสูงและมีต้นกำเนิดที่จุดศูนย์กลางของพวกเขาและไม่ว่าวัตถุหรือกล่องขอบเขตการหมุน
คำตอบ:
(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 เป็นคอขวดของประสิทธิภาพ
abs(5 - 10) * 2 < (10 + 4)
10 < 14
คุณจะต้องทำการปรับแต่งง่ายๆเพื่อให้สามารถใช้งานได้กับขนาดของมุมและขนาด
ใช้งานได้สำหรับสี่เหลี่ยมสองรูปแบบที่จัดชิดกับแกน 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- แต่ละแบบอื่น ๆ หรือไม่ /
ถ้าคุณต้องการกล่องขอบวัตถุให้ลองบทช่วยสอนนี้ในทฤษฎีการแยกแกนโดย metanet: http://www.metanetsoftware.com/technique/tutorialA.html
SAT ไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุด แต่ก็ค่อนข้างง่าย คุณพยายามที่จะหาบรรทัดเดียว (หรือเครื่องบินถ้า 3D) ที่จะแยกวัตถุของคุณ หากบรรทัดนี้มีอยู่มันรับประกันว่าจะต้องมีการ paralell ไปที่ขอบของหนึ่งในกล่องของคุณดังนั้นคุณจึงวนซ้ำผ่านการทดสอบขอบทั้งหมดเพื่อดูว่ามันแยกกล่อง
นอกจากนี้ยังใช้งานได้กับกล่องที่จัดแนวแกนโดย จำกัด เฉพาะแกน x / y
DoBoxesIntersect ด้านบนเป็นวิธีแก้ปัญหาที่ดี อย่างไรก็ตามหากคุณมีกล่องจำนวนมากคุณยังคงมีปัญหา O (N ^ 2) และคุณอาจพบว่าคุณต้องทำอะไรบางอย่างนอกเหนือจากที่ Kaj อ้างถึง (ในวรรณคดีการตรวจจับการชนกันแบบ 3 มิตินี่เป็นที่รู้จักกันว่ามีทั้งอัลกอริธึมกว้างและเฟสแคบเราจะทำบางสิ่งอย่างรวดเร็วเพื่อค้นหาการทับซ้อนที่เป็นไปได้ทั้งหมด คู่คือคู่แท้)
อัลกอริทึมแบบกว้างที่ฉันเคยใช้มาก่อนคือ "การกวาดและตัด"; สำหรับ 2D คุณจะต้องรักษารายการที่แยกไว้สองรายการของจุดเริ่มต้นและจุดสิ้นสุดของแต่ละกล่อง ตราบใดที่การเคลื่อนไหวของกล่องไม่ใช่ขนาดกล่องจากเฟรมหนึ่งไปหนึ่งเฟรมลำดับของรายการเหล่านี้จะไม่เปลี่ยนแปลงมากนักดังนั้นคุณสามารถใช้การเรียงลำดับฟองหรือการแทรกเพื่อรักษามันไว้ หนังสือ "การแสดงผลตามเวลาจริง" มีการเขียนที่ดีเกี่ยวกับการเพิ่มประสิทธิภาพที่คุณสามารถทำได้ แต่มันจะลดลงถึงเวลา O (N + K) ในระยะกว้างสำหรับกล่อง N, K ที่ทับซ้อนกันและมีโลกแห่งความเป็นเลิศที่ยอดเยี่ยม ประสิทธิภาพถ้าคุณสามารถจ่าย N ^ 2 booleans เพื่อติดตามว่ามีกล่องใดบ้างที่ตัดกันจากเฟรมหนึ่งไปอีกเฟรมหนึ่ง จากนั้นคุณจะมีเวลาโดยรวมของ O (N + K ^ 2) ซึ่งก็คือ << O (N ^ 2) ถ้าคุณมีกล่องจำนวนมาก แต่มีการทับซ้อนกันเพียงไม่กี่ตัว
คณิตศาสตร์จำนวนมากตรงนี้สำหรับปัญหาที่ง่ายมากสมมติว่าเรามี 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);
}
คำตอบของ 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);
}
ขึ้นอยู่กับปัญหาที่คุณพยายามแก้ไขคุณอาจจะดีกว่าในการติดตามวัตถุของคุณในขณะที่คุณย้ายวัตถุเช่นเก็บรายการของตำแหน่ง x เริ่มต้นและสิ้นสุดและหนึ่งสำหรับตำแหน่งเริ่มต้นและสิ้นสุด y หากคุณต้องทำการตรวจสอบที่ทับซ้อนกันจำนวนมากดังนั้นจึงจำเป็นต้องปรับให้เหมาะสมคุณสามารถใช้สิ่งนี้เพื่อผลประโยชน์ของคุณเนื่องจากคุณสามารถค้นหาผู้ที่กำลังจะปิดทางด้านซ้ายของคุณได้ทันทีทุกคนที่ตอนจบอยู่ทางด้านซ้าย ทันที ใช้เหมือนกันสำหรับด้านบนด้านล่างและขวา
การทำบัญชีต้องใช้เวลาแน่นอนดังนั้นจึงเหมาะสำหรับสถานการณ์ที่มีวัตถุเคลื่อนที่น้อย แต่มีการตรวจสอบที่ทับซ้อนกันจำนวนมาก
อีกตัวเลือกหนึ่งคือ hashing เชิงพื้นที่ที่คุณฝากข้อมูลวัตถุตามตำแหน่งโดยประมาณ (ขนาดอาจใส่ em ในที่เก็บหลาย ๆ อัน) แต่มีอีกครั้งเฉพาะในกรณีที่มีวัตถุจำนวนมากโดยมีวัตถุค่อนข้างน้อยต่อการทำซ้ำเนื่องจากต้นทุนการทำบัญชี
โดยทั่วไปสิ่งใดที่หลีกเลี่ยง (n * n) / 2 (ถ้าคุณตรวจสอบวัตถุ a กับ b คุณจะไม่ต้องตรวจสอบ b กับชัด ๆ ) ช่วยมากกว่าการเพิ่มประสิทธิภาพการตรวจสอบกล่องขอบเขต หากการตรวจสอบขอบเขตกล่องเป็นปัญหาคอขวดฉันขอแนะนำอย่างจริงจังให้ค้นหาวิธีแก้ไขปัญหาอื่น ๆ
ระยะห่างระหว่างจุดศูนย์กลางไม่เท่ากับระยะห่างระหว่างมุม (เมื่อมีกล่องหนึ่งอยู่ในอีกมุมหนึ่ง) ดังนั้นโดยทั่วไปคำตอบนี้เป็นคำตอบที่ถูกต้อง (ฉันคิดว่า)
ระยะห่างระหว่างศูนย์ (สำหรับ, พูด, 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));
นี่คือการดำเนินงานของฉันใน 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
)
วิธีที่เร็วที่สุดคือการรวมค่าทั้ง 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 และอื่น ๆ