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.
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.
Câu trả lời:
(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.
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.
Đ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 /
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.
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.
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);
}
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);
}
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 đề.
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));
Đâ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_VALUE
và Integer.MIN_VALUE
).
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_ps
vớ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.