Cách nhanh nhất để làm việc ra giao lộ hộp giới hạn 2D là gì?


62

Giả sử rằng mỗi đối tượng Box có các thuộc tính x, y, chiều rộng, chiều cao và có nguồn gốc ở trung tâm của chúng và không các đối tượng cũng như các hộp giới hạn xoay.


Là các hộp giới hạn trục hoặc đối tượng liên kết?
tenpn

3
Khi bạn đặt câu hỏi này, chắc chắn bạn sẽ cần kiểm tra các loại giao lộ khác trong tương lai;). Do đó tôi đề nghị DANH SÁCH về giao lộ Object / Object. Bảng cung cấp các giao điểm giữa tất cả các loại đối tượng phổ biến (hộp, hình cầu, hình tam giác, hình tròn, hình nón, ...) trong các tình huống tĩnh cũng như động.
Dave O.

2
Vui lòng viết lại câu hỏi của bạn cho giới hạn. Từ quan điểm của tôi hộp ngụ ý một đối tượng 3d.
Dave O.

Câu trả lời:


55

(Mã giả C-ish - tối ưu hóa ngôn ngữ thích hợp khi thích hợp)

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

Bằng tiếng Anh: Trên mỗi trục, kiểm tra xem tâm của các hộp có đủ gần để chúng giao nhau không. Nếu chúng giao nhau trên cả hai trục, thì các hộp giao nhau. Nếu họ không, thì họ không.

Bạn có thể thay đổi <'s thành <= nếu bạn muốn tính điểm chạm cạnh là giao nhau. Nếu bạn muốn một công thức chỉ chạm cạnh cụ thể, bạn không thể sử dụng == - nó sẽ cho bạn biết nếu các góc chạm vào chứ không phải nếu các cạnh chạm vào. Bạn muốn làm một cái gì đó tương đương logic return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b).

Điều đáng nói là bạn có thể tăng tốc độ nhỏ nhưng đáng kể bằng cách lưu trữ nửa chiều rộng và nửa chiều cao bên cạnh (hoặc thay vì) toàn bộ chiều rộng và chiều cao đầy đủ. Mặt khác, rất hiếm khi giao lộ hộp giới hạn 2d trở thành nút cổ chai hiệu năng.


9
Điều này rõ ràng giả định các hộp được căn chỉnh theo trục.
tenpn

1
Abs không nên đặc biệt chậm - ít nhất là chậm hơn một điều kiện, ít nhất, và cách duy nhất để làm điều đó mà không có abs (mà tôi biết) liên quan đến các điều kiện bổ sung.
ZorbaTHut

4
Vâng, nó không giả định hộp căn chỉnh trục. Tuy nhiên, các cấu trúc như được mô tả không có bất kỳ cách nào để chỉ ra sự quay, vì vậy tôi cảm thấy nó an toàn.
ZorbaTHut

3
Dưới đây là một số mẹo hay để tăng tốc độ tính toán trong Actioncript (chủ yếu là số nguyên calc): lab.poly Cross.de/2007/05/10/bitwise-gems-fast-integer-math Tôi đang đăng bài này, vì nó cũng chứa nhanh hơn thay thế cho Math.abs (), có xu hướng làm chậm mọi thứ trong Actioncript thực sự (tất nhiên là nói về hiệu suất của các công cụ quan trọng).
bummzack

2
Bạn đang thiếu rằng họ có nguồn gốc ở trung tâm, không phải ở cạnh trái. Một hộp chạy từ 0 đến 10 sẽ thực sự có "x = 5", trong khi một hộp chạy từ 8 đến 12 sẽ có "x = 10". Bạn kết thúc với abs(5 - 10) * 2 < (10 + 4)=> 10 < 14. Bạn sẽ cần phải thực hiện một số điều chỉnh đơn giản để làm cho nó hoạt động với góc to và kích thước.
ZorbaTHut

37

Điều này làm việc cho hai hình chữ nhật thẳng hàng với trục X và Y.
Mỗi hình chữ nhật có các thuộc tính:
"trái", tọa độ x của cạnh trái,
"đỉnh", tọa độ y của mặt trên của nó,
"phải", tọa độ x của cạnh phải,
"dưới", tọa độ y của mặt dưới của nó,

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

Lưu ý rằng điều này được thiết kế cho một hệ tọa độ trong đó trục + y chỉ xuống và trục + x được hướng sang phải (nghĩa là tọa độ màn hình / pixel điển hình). Để thích ứng điều này với một hệ thống cartes điển hình trong đó + y được hướng lên trên, các phép so sánh dọc theo trục dọc sẽ được đảo ngược, ví dụ:

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

Ý tưởng là nắm bắt tất cả các điều kiện có thể mà các hình chữ nhật sẽ không trùng nhau, sau đó phủ nhận câu trả lời để xem chúng có bị chồng chéo không. Bất kể hướng của các trục là gì, dễ dàng thấy rằng hai hình chữ nhật sẽ không trùng nhau nếu:

  • cạnh trái của r2 xa hơn so với cạnh phải của r1

     ________     ________
    |        |   |        |
    |   r1   |   |   r2   |
    |        |   |        |
    |________|   |________|
  • hoặc cạnh phải của r2 nằm xa hơn so với cạnh trái của r1

     ________     ________
    |        |   |        |
    |   r2   |   |   r1   |
    |        |   |        |
    |________|   |________|
  • hoặc cạnh trên của r2 nằm dưới cạnh dưới của r1

     ________ 
    |        |
    |   r1   |
    |        |
    |________|
     ________ 
    |        |
    |   r2   |
    |        |
    |________|
  • hoặc cạnh dưới của r2 nằm trên cạnh trên của r1

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

Hàm ban đầu - và một mô tả thay thế về lý do tại sao nó hoạt động - có thể được tìm thấy ở đây: http://tekpool.wordpress.com/2006/10/11/ronymous-intersection-determine-if-two-given-ripseles-intersect- lẫn nhau hoặc không /


1
Đáng ngạc nhiên trực quan và cho thấy một lần nữa rằng khi tìm câu trả lời là quá khó, cố gắng tìm câu trả lời cho một câu hỏi ngược lại có thể giúp bạn ra ngoài. Cảm ơn!
Lodewijk

1
Bạn nên đề cập rằng trục y chỉ xuống (như trong ảnh). Mặt khác, bất đẳng thức r2.top> r1.bottom và r2.bottom <r1.top cần phải được đảo ngược.
user1443778

@ user1443778 bắt tốt! Tôi đã đi trước và giải thích một cách lỏng lẻo logic đằng sau thuật toán này theo cách thức bất khả tri của hệ thống.
Ponkadoodle

11

Nếu bạn muốn các hộp giới hạn được căn chỉnh đối tượng, hãy thử hướng dẫn này về định lý trục tách biệt của metanet: http://www.metanetsoftware.com/technique/tutorialA.html

SAT không phải là giải pháp nhanh nhất, nhưng nó tương đối đơn giản. Bạn đang cố gắng tìm một dòng duy nhất (hoặc một mặt phẳng nếu 3D) sẽ tách biệt các đối tượng của bạn. Nếu dòng này tồn tại, nó chắc chắn sẽ được đặt song song với cạnh của một trong các hộp của bạn, vì vậy bạn lặp lại qua tất cả các cạnh để kiểm tra xem nó có ngăn cách các hộp không.

Điều này cũng hoạt động cho các hộp căn chỉnh trục bằng cách ràng buộc chỉ trục x / y.


Tôi đã không nghĩ đến việc xoay vòng, nhưng cảm ơn đó là một liên kết thú vị.
Iain

5

DoBoxesIntersect ở trên là một giải pháp cặp đôi tốt. Tuy nhiên, nếu bạn có nhiều hộp, bạn vẫn gặp vấn đề O (N ^ 2) và bạn có thể thấy bạn cần phải làm gì đó trên đầu trang giống như những gì Kaj đề cập. . cặp là cặp thực tế.)

Thuật toán pha rộng mà tôi đã sử dụng trước đây là "quét và cắt tỉa"; đối với 2D, bạn sẽ duy trì hai danh sách được sắp xếp bắt đầu và kết thúc của mỗi hộp. Miễn là chuyển động của hộp không >> tỷ lệ hộp từ khung này sang khung khác, thứ tự của các danh sách này sẽ không thay đổi nhiều, và vì vậy bạn có thể sử dụng bong bóng hoặc sắp xếp chèn để duy trì nó. Cuốn sách "Kết xuất thời gian thực" có một bài viết hay về tối ưu hóa bạn có thể làm, nhưng nó rút ngắn thời gian O (N + K) trong giai đoạn rộng, cho các hộp N, K trong đó trùng lặp và với thế giới thực tuyệt vời hiệu suất nếu bạn có thể đủ khả năng N ^ 2 booleans để theo dõi các cặp hộp nào giao nhau từ khung này sang khung khác. Sau đó, bạn có tổng thời gian O (N + K ^ 2), đó là << O (N ^ 2) nếu bạn có nhiều hộp nhưng chỉ có một vài chồng chéo.


5

Rất nhiều phép toán ở đây cho một vấn đề rất đơn giản, giả sử rằng chúng ta có 4 điểm được xác định cho một trực tràng, trên, trái, dưới, phải ...

Trong trường hợp xác định xem 2 rits có va chạm hay không, chúng ta chỉ cần xem xét tất cả các cực trị có thể có thể ngăn va chạm hay không, nếu không có hai điều này được đáp ứng, thì 2 rects PHẢI va chạm, nếu bạn muốn bao gồm các va chạm biên, chỉ cần thay thế> và < với thích hợp> = và = <.

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

Tôi thực sự không chắc tại sao đây không phải là câu trả lời được bình chọn hàng đầu. Nó đơn giản, chính xác và hiệu quả.
3Dave

3

Phiên bản thay thế của câu trả lời của 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);
}

Trên thực tế, số học hoạt động tốt trong cả hai cách. Bạn có thể thực hiện bất kỳ thao tác số học nào ở hai bên của <và nó không thay đổi nó (nhân với một số âm có nghĩa là bạn phải thay đổi ít hơn, mặc dù vậy). Trong ví dụ đó, các hộp không nên va chạm. Nếu tâm của hộp A ở 1, nó kéo dài từ -4 đến 6. Hộp b ở giữa và kéo dài 7,5 đến 12,5, không có va chạm ở đó ... Bây giờ, phương pháp mà Wallacoloo đăng không chỉ đúng, nhưng sẽ chạy nhanh hơn trên các ngôn ngữ thực hiện đoản mạch, vì hầu hết các kiểm tra sẽ trả về sai dù sao, việc đoản mạch có thể bị cắt sau khi
Deleter

Vâng, tôi nhận ra điều này khi tôi thức dậy sáng nay. Chris bambusaled tôi với <> trộn của mình.
Iain

1
Hai vấn đề: thứ nhất, phân chia có xu hướng chậm hơn đáng kể so với nhân. Thứ hai, nếu các giá trị liên quan là số nguyên, điều này có thể dẫn đến một số vấn đề rút ngắn số nguyên (ax = 0, bx = 9, a. Thong = 9, b. Thong = 10: abs (0-9) <(9 + 10) / 2, 9 <19/2, 9 <9, hàm trả về sai mặc dù thực tế là các hộp chắc chắn giao nhau.)
ZorbaTHut

2

Tùy thuộc vào vấn đề bạn cố gắng giải quyết, bạn có thể theo dõi đối tượng của mình tốt hơn trong khi di chuyển chúng, tức là giữ một danh sách các vị trí bắt đầu và kết thúc x được sắp xếp và một vị trí cho các vị trí bắt đầu và kết thúc y. Nếu bạn phải thực hiện RẤT NHIỀU kiểm tra chồng chéo và do đó cần tối ưu hóa, bạn có thể sử dụng điều này cho lợi thế của mình, vì bạn có thể ngay lập tức tìm kiếm ai sẽ kết thúc bên trái của bạn, mọi người kết thúc ở bên trái có thể được cắt tỉa ngay lập tức. Áp dụng tương tự cho đầu, cuối và phải.
Việc ghi sổ tất nhiên tốn thời gian, vì vậy nó phù hợp hơn cho một tình huống có ít đối tượng chuyển động nhưng có nhiều kiểm tra chồng chéo.
Một optionh khác là băm không gian, trong đó bạn xô các đối tượng dựa trên vị trí gần đúng (kích thước có thể đặt chúng vào nhiều thùng), nhưng một lần nữa, chỉ khi có rất nhiều đối tượng, với số lượng tương đối ít di chuyển trong mỗi lần lặp do chi phí ghi sổ.
Về cơ bản bất cứ điều gì tránh (n * n) / 2 (nếu bạn kiểm tra đối tượng a so với b, bạn sẽ không phải kiểm tra b đối với một điều hiển nhiên) giúp nhiều hơn là tối ưu hóa kiểm tra hộp giới hạn. Nếu kiểm tra hộp giới hạn là một nút cổ chai, tôi thực sự khuyên bạn nên xem xét các giải pháp thay thế cho vấn đề.


2

Khoảng cách giữa các trung tâm không giống như khoảng cách giữa các góc (ví dụ khi một hộp nằm trong hộp khác), vì vậy, trong CHUNG, giải pháp này là chính xác (tôi nghĩ).

khoảng cách giữa các trung tâm (for, say, x): abs(x1+1/2*w1 - x2+1/2*w2)hoặc1/2 * abs(2*(x1-x2)+(w1-w2)

Khoảng cách tối thiểu là 1/2 w1 + 1/2 w2 or 1/2 (w1+w2). các nửa hủy bỏ như vậy ..

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

1
Điều gì với tuyên bố "trở lại" trong đó?
doppelgreener

1

Đây là triển khai của tôi trong Java với giả định kiến ​​trúc bổ sung twos . Nếu bạn không bổ sung twos, hãy sử dụng lệnh gọi hàm Math.abs tiêu chuẩn thay thế:

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

Giả sử một trình biên dịch nửa chừng / LLVM nội tuyến mở rộng các hàm này để tránh việc tung hứng ngăn xếp đắt tiền và tra cứu bảng v. Điều này sẽ thất bại đối với các giá trị đầu vào gần với cực trị 32 bit (nghĩa là Integer.MAX_VALUEInteger.MIN_VALUE).


0

Cách nhanh nhất là kết hợp cả 4 giá trị trong một thanh ghi vector.

Lưu trữ các hộp trong một vectơ với các giá trị sau [ min.x, min.y, -max.x, -max.y ]. Nếu bạn lưu trữ các hộp như thế này, kiểm tra giao lộ chỉ mất 3 hướng dẫn CPU:

_mm_shuffle_ps để sắp xếp lại hộp thứ hai lật tối thiểu và nửa tối đa.

_mm_xor_psvới số ma thuật _mm_set1_ps(-0.0f)để lật các dấu hiệu của cả 4 giá trị trong hộp thứ hai.

_mm_cmple_ps để so sánh tất cả 4 giá trị với nhau, so sánh hai thanh ghi sau:

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

Cuối cùng, nếu cần, _mm_movemask_psđể lấy kết quả ra khỏi đơn vị vectơ vào một thanh ghi vô hướng. Giá trị 0 có nghĩa là các hộp giao nhau. Hoặc nếu bạn có nhiều hơn 2 hộp thì không cần thiết, hãy để các giá trị trong các thanh ghi vector và sử dụng các thao tác bitwise để kết hợp các kết quả từ nhiều hộp.

Bạn chưa chỉ định ngôn ngữ cũng như nền tảng, nhưng hỗ trợ cho SIMD như thế này, hoặc rất giống nhau, có sẵn ở tất cả các nền tảng và ngôn ngữ. Trên di động, ARM có NEON SIMD với những thứ rất giống nhau. .NET có Vector128 trong không gian tên System.R.78.Intrinsics, v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.