쿼드 트리 대 그리드 기반 충돌 감지


27

나는 4 플레이어 협동 r 형 게임을 만들고 충돌 감지 코드를 구현하려고합니다. 충돌 감지를 처리하는 방법에 대한 많은 기사와 내용을 읽었지만 무엇을 처리해야할지 고민하고 있습니다. 쿼드 트리가 가장 일반적인 방법 인 것처럼 보이지만 일부 리소스에서는 그리드 기반 솔루션을 언급합니다. 이전 게임에서 탐지에 그리드를 사용했다는 점에 익숙하지만 실제로 쿼드 트리보다 낫습니까? 어느 것이 최고의 성능을 제공하는지 잘 모르겠으며, 약간의 벤치 마크를 실행했지만 두 솔루션간에 큰 차이는 없습니다.

하나는 다른 것보다 낫습니까? 아니면 더 우아한? 어떤 것을 사용 해야하는지 잘 모르겠습니다.

모든 조언을 환영합니다. 감사.

답변:


31

정답은 실제로 디자인하고있는 게임에 따라 조금씩 달라 지므로, 다른 게임을 선택하는 것은 실제로 특정 게임에서 어느 것이 더 시간이나 공간 효율적인지 알아 내기 위해 두 가지를 모두 구현하고 프로파일 링을 수행해야합니다.

그리드 감지는 움직이는 물체와 정적 배경 사이의 충돌 감지에만 적용되는 것으로 보입니다. 이것의 가장 큰 장점은 정적 배경이 연속 메모리 배열로 표시되며 여러 번의 읽기를 수행해야하는 경우 (엔티티가 그리드에서 둘 이상의 셀을 커버하기 때문에) 각 충돌 조회는 로컬 성이 좋은 O (1)입니다. 정적 배경이 크면 단점은 그리드에 공간이 낭비 될 수 있다는 것입니다.

대신 정적 배경을 쿼드 트리로 표시하면 개별 조회 비용이 증가하지만 배경의 큰 블록이 적은 공간을 차지하기 때문에 메모리 요구 사항이 줄어들므로 더 많은 배경이 은닉처. 이러한 구조에서 조회를 수행하는 데 10 배 많은 읽기가 필요한 경우에도 캐시에 모두 있으면 캐시 미스가있는 단일 조회보다 10 배 더 빠릅니다.

내가 선택에 직면했다면? 그리드 구현을 사용하는 것은 어리석은 간단하기 때문에 다른 흥미로운 문제에 더 잘 적응할 수 있기 때문입니다. 게임이 약간 느리게 실행되는 것을 알게되면 프로파일 링을 수행하고 어떤 도움이 필요한지 살펴 보겠습니다. 게임이 충돌 감지에 많은 시간을 소비하는 것처럼 보이면 쿼드 트리와 같은 다른 구현을 시도하고 (모든 쉬운 수정을 먼저 사용한 후) 도움이되는지 확인하십시오.

편집 : 그리드 충돌 감지가 여러 모바일 엔티티의 충돌 감지와 어떤 관련이 있는지에 대한 단서가 없지만 대신 공간 인덱스 (Quadtree)가 반복 솔루션에서 감지 성능을 향상시키는 방법에 대해 대답합니다. 순진한 (일반적으로 완벽하게 훌륭한) 솔루션은 다음과 같습니다.

foreach actor in actorList:
    foreach target in actorList:
        if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

이것은 총알과 우주선, 외계인을 포함하여 현재 게임에 존재하는 배우의 수와 함께 O (n ^ 2) 정도의 성능을 가지고 있습니다. 작은 정적 장애물도 포함 할 수 있습니다.

이것은 그러한 항목의 수가 합리적으로 적은 한 환상적으로 잘 작동하지만 확인할 수백 개가 넘는 객체가있을 때 약간 나빠 보이기 시작합니다. 10 개의 개체는 100 번의 충돌 검사를, 100 개의 결과는 10,000 개의 검사를 수행합니다. 1000은 100 만 건의 결과입니다.

쿼드 트리와 같은 공간 인덱스는 기하학적 관계에 따라 수집 한 항목을 효율적으로 열거 할 수 있습니다. 충돌 알고리즘을 다음과 같이 변경합니다.

foreach actor in actorList:
    foreach target in actorIndex.neighbors(actor.boundingbox):
       if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

이것의 효율성 (엔티티의 균일 한 분포를 가정) : 일반적으로 O (n ^ 1.5 log (n))입니다. 인덱스는 통과하는 데 log (n) 비교가 필요하기 때문에 비교할 sqrt (n) 이웃이있을 것입니다. 확인해야 할 액터가 n 개 있습니다. 그러나 실제로 충돌이 발생하면 대부분 객체 중 하나가 삭제되거나 충돌에서 멀어지기 때문에 이웃의 수는 항상 매우 제한적입니다. 따라서 O (n log (n)) 만 얻습니다. 10 개의 엔터티에 대해 10 번의 비교를 수행하고, 100에 대해 200을, 1000에 대해 3000을 수행합니다.

정말 영리한 인덱스는 이웃 검색을 대량 반복과 결합하고 각 교차 엔티티에서 콜백을 수행 할 수도 있습니다. 인덱스가 n 번 쿼리되지 않고 한 번 스캔되므로 약 O (n)의 성능을 제공합니다.


"정적 배경"이라고 말할 때 무엇을 참조하는지 잘 모르겠습니다. 내가 다루는 것은 기본적으로 2D 슈팅 게임이므로 우주선과 외계인, 총알 및 벽과의 충돌 감지입니다.
dotminic

2
내 개인 "훌륭한 답변"배지를 받았습니다.
Felixyz

어리석게 들릴지 모르지만 실제로 쿼드 트리를 사용하여 객체가 충돌을 테스트해야하는 다른 객체를 어떻게 선택합니까? 이것이 어떻게 수행되는지 확신 할 수 없습니다. 두 번째 질문이 나옵니다. 노드에 다른 노드의 이웃이 아닌 객체가 있지만 객체가 몇 개의 노드에 걸쳐있을 정도로 충분히 크다고 가정 해보십시오. 나무가 그렇지 않다고 생각할 수 있기 때문에 실제 충돌을 어떻게 확인할 수 있습니까? "먼 거리"노드에서 객체와 충돌 할 수있을 정도로 가까이 있습니까? 노드에 완전히 맞지 않는 객체를 부모 노드에 보관해야합니까?
dotminic

2
쿼트 트리는 겹치는 경계 상자 검색에있어 본질적으로 차선책입니다. 이를위한 최선의 선택은 일반적으로 R- 트리입니다. 쿼드 트리의 경우 대부분의 객체가 대략 점 모양이면 객체를 내부 노드에 유지하고 퍼지 이웃 검색에서 정확한 충돌 테스트를 수행하는 것이 합리적입니다. 인덱스에있는 대부분의 객체가 크고 충돌없이 겹치는 경우 쿼드 트리가 적합하지 않을 수 있습니다. 이에 대한 자세한 기술적 인 질문이있는 경우 stackoverflow.com에게 복용 고려해야한다
SingleNegationElimination

이 모든 것은 꽤 혼란 스럽습니다! 정보에 대해서 감사드립니다.
dotminic

3

고대 실을 부활 시켜서 죄송하지만 IMHO 평범한 오래된 격자는 이러한 경우에 자주 사용되지 않습니다. 셀 삽입 / 제거가 더러워지면 그리드에 많은 이점이 있습니다. 격자는 희소 표현을 최적화하는 것을 목표로하지 않기 때문에 셀 해제에 신경 쓸 필요가 없습니다. 쿼드 트리를 그리드로 교체하여 레거시 코드베이스에서 여러 요소를 선택하는 데 걸리는 시간을 1200ms 이상에서 20ms로 줄였습니다. 공평하게, 그 쿼드 트리는 실제로 구현이 제대로 이루어지지 않았으므로 요소에 대한 리프 노드 당 별도의 동적 배열을 저장했습니다.

내가 매우 유용하다고 생각하는 다른 하나는 도형을 그리기위한 클래식 래스터 화 알고리즘을 사용하여 그리드를 검색 할 수 있다는 것입니다. 예를 들어 Bresenham 선 래스터 화를 사용하여 선과 교차하는 요소를 검색하고 스캔 선 래스터 화를 사용하여 어떤 셀이 다각형과 교차하는지 등을 검색 할 수 있습니다. 격자에서 움직이는 물체에 대한 교차점을 감지하는 데 사용하면서 픽셀을 이미지에 플롯하는 데 사용하는 최적화 된 코드입니다.

그리드를 효율적으로 만들기 위해서는 그리드 셀당 32 비트 이상이 필요하지 않습니다. 4MB 이하로 백만 개의 셀을 저장할 수 있어야합니다. 각 그리드 셀은 셀의 첫 번째 요소를 인덱싱 할 수 있으며 셀의 첫 번째 요소는 셀의 다음 요소를 인덱싱 할 수 있습니다. 모든 단일 셀에 일종의 본격적인 컨테이너를 저장하는 경우 메모리 사용 및 할당이 폭발적으로 증가합니다. 대신 당신은 할 수 있습니다 :

struct Node
{
    int32_t next;
    ...
};

struct Grid
{
     vector<int32_t> cells;
     vector<Node> nodes;
};

이렇게 :

여기에 이미지 설명을 입력하십시오

좋아, 단점에. 나는 그리드에 대한 편견과 선호 로이 사실을 인정하고 있지만 그들의 주된 단점은 희소하지 않다는 것입니다.

좌표가 주어진 특정 그리드 셀에 액세스하는 것은 일정한 시간이며 더 저렴한 트리를 내릴 필요는 없지만 그리드가 밀도가 낮고 희박하지 않으므로 필요한 것보다 많은 셀을 확인해야 할 수 있습니다. 데이터가 매우 희소하게 분포 된 상황에서 그리드는 선이나 채워진 다각형, 사각형 또는 경계 원과 교차하는 요소를 파악하기 위해 더 많은 방법을 확인해야 할 수 있습니다. 그리드는 완전히 비어있는 경우에도 해당 32 비트 셀을 저장해야하며 모양 교차 쿼리를 수행 할 때 빈 셀이 모양과 교차하는지 확인해야합니다.

쿼드 트리의 주요 이점은 당연히 스파 스 데이터를 저장하고 필요한만큼만 세분화하는 기능입니다. 즉, 특히 모든 프레임 주위에서 움직이는 것이있는 경우 실제로 구현하기가 더 어렵습니다. 트리는 하위 노드를 매우 효율적으로 세분화하고 해제해야합니다. 그렇지 않으면 부모-> 자식 링크를 저장하기 위해 오버 헤드를 낭비하는 밀도가 높은 그리드로 저하됩니다. 그리드에 대해 위에서 설명한 것과 매우 유사한 기술을 사용하여 효율적인 쿼드 트리를 구현하는 것이 가능하지만 일반적으로 시간이 많이 걸립니다. 그리고 그리드에서 내가하는 방식으로 수행하면 쿼드 트리 노드의 4 명의 자식이 모두 연속적으로 저장되도록 보장 할 수있는 능력이 상실되므로 반드시 최적은 아닙니다.

또한 전체 장면의 많은 부분에 걸쳐 많은 수의 큰 요소가있는 경우 쿼드 트리와 그리드는 웅장한 작업을 수행하지 않지만 최소한 그리드는 평평하게 유지되고 이러한 경우 n 도로 세분화되지 않습니다 . 쿼드 트리는 요소를 분기에 저장해야하며 이러한 경우를 합리적으로 처리하기 위해 잎뿐만 아니라 미친 것처럼 세분화하고 품질을 매우 빠르게 저하 시키려고합니다. 가장 넓은 범위의 컨텐츠를 처리하려면 쿼드 트리로 처리해야 할 병리학 적 사례가 더 있습니다. 예를 들어, 실제로 쿼드 트리를 트립 할 수있는 또 다른 경우는 일치하는 요소의 보트로드가있는 경우입니다. 이 시점에서 일부 사람들은 쿼드 트리의 깊이 제한을 설정하여 무한히 세분화하는 것을 방지합니다. 그리드는 괜찮은 일을한다는 호소력이 있습니다.

안정성과 예측 가능성은 게임 상황에서도 유리합니다. 때로는 드문 경우 시나리오에서 프레임 속도가 가끔씩 빨라질 수 있기 때문에 일반적인 경우에 가능한 가장 빠른 솔루션을 원치 않을 수도 있기 때문입니다. 그러나 이러한 딸꾹질을 일으키지 않으며 프레임 속도를 부드럽고 예측 가능하게 유지합니다. 그리드에는 후자의 품질이 있습니다.

모든 말로, 나는 그것이 프로그래머에게 달려 있다고 생각합니다. grid vs. quad-tree 또는 octree vs. kd-tree vs. BVH와 같은 것들로, 내 투표는 어떤 데이터 구조를 사용하든 매우 효율적인 솔루션을 만드는 기록을 가진 가장 많은 개발자에게 있습니다. 멀티 스레딩, SIMD, 캐시 친화적 메모리 레이아웃 및 액세스 패턴과 같은 마이크로 레벨에도 많은 것들이 있습니다. 어떤 사람들은 그 마이크로를 고려할 수도 있지만 반드시 마이크로 영향을 줄 필요는 없습니다. 이러한 것들은 한 솔루션에서 다른 솔루션으로 100 배의 차이를 만들 수 있습니다. 그럼에도 불구하고 개인적으로 며칠이 주어졌고 모든 프레임 주위를 움직이는 요소의 충돌 감지를 빠르게 가속화하기 위해 데이터 구조를 구현해야한다고 들었다면 쿼드보다 그리드를 구현하는 짧은 시간에 더 나을 것입니다 -나무.

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