2D 경계 상자 교차점을 해결하는 가장 빠른 방법은 무엇입니까?


62

각 Box 객체에는 x, y, width, height 속성이 있고 가운데에 원점이 있으며 객체와 경계 상자가 회전하지 않는다고 가정합니다.


이 축 또는 객체 정렬 경계 상자입니까?
tenpn

3
이 질문을 할 때는 앞으로 다른 유형의 교차로를 테스트해야합니다.;). 따라서 객체 / 객체 교차에 대한 목록을 제안 합니다. 이 표는 정적 및 동적 상황에서 널리 사용되는 모든 객체 유형 (상자, 구, 삼각형, 사이클린 더, 원뿔 등) 사이의 교차점을 제공합니다.
Dave O.

2
질문을 경계 수정으로 바꾸십시오. 내 관점에서 상자는 3D 개체를 의미합니다.
Dave O.

답변:


55

(C-ish 의사 코드-언어 최적화를 적절하게 조정)

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

영어 : 각 축에서 상자의 중심이 교차 할만큼 충분히 가까운 지 확인하십시오. 두 축이 서로 교차하면 상자가 교차합니다. 그렇지 않으면 그렇지 않습니다.

가장자리 터치를 교차로 계산하려면 <를 <=로 변경할 수 있습니다. 특정 가장자리 터치 전용 수식을 원하면 ==를 사용할 수 없습니다. 가장자리가 닿으면 모서리가 닿는 지 여부를 알려줍니다. 논리적으로에 해당하는 작업을 수행하려고합니다 return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b).

전체 너비와 전체 높이에 추가하여 (또는 대신에) 절반 너비와 절반 높이를 저장하면 작지만 상당한 속도 증가를 얻을 수 있습니다. 반면에 2D 바운딩 박스 교차로가 성능 병목 현상을 일으키는 경우는 거의 없습니다.


9
이것은 분명히 상자가 축 정렬되어 있다고 가정합니다.
tenpn

1
Abs는 특히 느리지 않아야합니다. 적어도 조건부보다 느리지 않아야하며 abs (내가 알고있는)없이 수행하는 유일한 방법은 추가 조건이 필요합니다.
ZorbaTHut

4
예, 축 정렬 상자를 가정합니다. 설명 된 구조에는 회전을 나타내는 방법이 없으므로 안전하다고 느꼈습니다.
ZorbaTHut

3
여기에 액션 스크립트 (주로 정수 CALC)에서 calulations을 가속화하기위한 좋은 팁은 다음과 같습니다 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

객체 정렬 경계 상자를 원하는 경우 메타넷을 기준으로 분리 축 정리 에서이 자습서를 시도하십시오 . http://www.metanetsoftware.com/technique/tutorialA.html

SAT는 가장 빠른 솔루션은 아니지만 비교적 간단합니다. 객체를 분리하는 단일 선 (또는 3D면 평면)을 찾으려고합니다. 이 선이 있으면 상자 중 하나의 가장자리에 평행하게 표시되도록 보증되므로 모든 가장자리 테스트를 반복하여 상자가 분리되는지 확인하십시오.

이것은 x / y 축으로 만 제한하여 축 정렬 상자에서도 작동합니다.


나는 회전을 생각하지 않았지만, 그것은 흥미로운 링크입니다.
Iain

5

위의 DoBoxesIntersect는 좋은 쌍별 솔루션입니다. 그러나 상자가 많은 경우에도 여전히 O (N ^ 2) 문제가 발생하며 Kaj가 말하는 것과 같은 것 외에 무언가를 수행해야 할 수도 있습니다. (3D 충돌 감지 문헌에서 이것은 넓은 위상 및 좁은 위상 알고리즘을 모두 사용하는 것으로 알려져 있습니다. 가능한 모든 중첩 쌍을 찾기 위해 실제로 빠른 작업을 수행 한 다음 가능한 경우 더 비싼 작업을 수행합니다 쌍은 실제 쌍입니다.)

이전에 사용했던 광대역 알고리즘은 "스윕 앤 프룬"입니다. 2D의 경우 각 상자의 시작과 끝의 두 가지 정렬 된 목록을 유지해야합니다. 박스 이동이 프레임마다 >> 박스 스케일이 아닌 한,이 목록의 순서는 크게 변하지 않으므로 버블 또는 삽입 정렬을 사용하여 유지할 수 있습니다. "Real-Time Rendering"이라는 책은 여러분이 할 수있는 최적화에 대한 훌륭한 글을 가지고 있지만, K가 겹치는 N 상자에 대해 광범위한 단계에서 O (N + K) 시간으로 요약됩니다. 프레임 간 교차하는 상자 쌍을 추적하기 위해 N ^ 2 부울을 감당할 수있는 경우 성능. 그런 다음 전체 상자에 O (N + K ^ 2) 시간이 있습니다. 상자가 많지만 겹치는 부분이 많으면 << O (N ^ 2)입니다.


5

매우 간단한 문제에 대한 많은 수학, 우리는 rect, top, left, bottom, right에 대해 4 점이 결정되었다고 가정합니다.

2 개의 rect가 충돌하는지 여부를 결정하는 경우 충돌을 방지 할 수있는 가능한 모든 극단 만 확인하면됩니다. 만약 충돌이 발생하지 않으면 2 개의 rect가 충돌해야합니다. 경계 충돌을 포함하려면> 및 < 적절한> = 및 = <.

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

솔직히 이것이 왜 투표권이 없는지 잘 모르겠습니다. 간단하고 정확하며 효율적입니다.
3Dave

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

네, 오늘 아침에 일어 났을 때 이것을 깨달았습니다. Chris는 그의 <> 믹스와 함께 나를 Bambusaled했다.
Iain

1
두 가지 문제 : 첫째, 나눗셈이 곱셈보다 현저히 느리다는 경향이 있습니다. 둘째, 관련된 값이 정수이면 일부 정수 잘림 문제가 발생할 수 있습니다 (ax = 0, bx = 9, a.width = 9, b.width = 10 : abs (0-9) <(9 + 10) / 2, 9 <19/2, 9 <9, 상자가 확실히 교차한다는 사실에도 불구하고 함수는 false를 반환합니다.)
ZorbaTHut

2

해결하려는 문제에 따라 객체를 이동하는 동안 추적하는 것이 좋습니다. 즉, 정렬 된 x 시작 및 종료 위치 목록과 시작 및 종료 y 위치에 대한 목록을 유지하는 것이 좋습니다. 많은 중복 검사를 수행해야하므로 최적화해야하는 경우, 왼쪽에 가까운 결말을 찾는 사람을 즉시 찾을 수 있으므로 끝까지가는 모든 사람은 정리할 수 있습니다. 바로. 위, 아래 및 오른쪽에도 동일하게 적용됩니다.
부기 비용은 물론 시간이 많이 걸리므로 움직이는 물체는 적지 만 겹침 검사가 많은 상황에 더 적합합니다.
또 다른 옵션은 공간 해싱으로, 대략적인 위치 (크기는 여러 버킷에 넣을 수 있음)를 기준으로 객체를 버킷 화하지만, 예약 유지 비용으로 인해 반복 당 이동하는 객체가 거의없는 많은 객체가있는 경우에만 다시 나타납니다.
기본적으로 (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

다음 은 2 보완 아키텍처를 가정하여 Java로 구현 한 것입니다 . two-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;
}

절반 정도의 컴파일러 / LLVM 인라인을 가정하면 이러한 기능이 확장되어 값 비싼 스택 저글링 및 v- 테이블 조회를 피할 수 있습니다. 이것은 실패 32 비트 극단 (즉, 부근에있는 입력 값 Integer.MAX_VALUEInteger.MIN_VALUE).


0

가장 빠른 방법은 단일 벡터 레지스터에서 4 개의 값을 모두 결합하는 것입니다.

상자를 다음 값을 가진 벡터에 저장하십시오 [ min.x, min.y, -max.x, -max.y ]. 이와 같은 상자를 저장하면 교차 테스트는 3 개의 CPU 명령 만 수행합니다.

_mm_shuffle_ps 최소 및 최대 절반을 뒤집어 두 번째 상자를 재정렬합니다.

_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은 상자가 교차 함을 의미합니다. 또는 상자가 두 개 이상인 경우 필요하지 않은 경우 벡터 레지스터에 값을 그대로두고 비트 단위 연산을 사용하여 여러 상자의 결과를 결합하십시오.

언어 나 플랫폼을 지정하지 않았지만 이와 유사한 SIMD에 대한 지원은 모든 플랫폼과 언어에서 사용할 수 있습니다. 모바일에서 ARM은 매우 유사한 기능을 가진 NEON SIMD를 보유하고 있습니다. .NET은 System.Runtime.Intrinsics 네임 스페이스 등에 Vector128이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.