n 개의 객체로 구성된 시스템의 충돌 검사 효율성을 높일 수있는 방법이 있습니까?


9

많은 화면 개체로 구성된 게임을 만들고 있는데 그 중 하나는 플레이어입니다. 반복 할 때마다 충돌하는 개체를 알아야합니다.

나는 이런 식으로했다 :

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

이것은 O (n ^ 2)를 가지고 있는데, 그것은 나쁘다고 들었습니다. 이 작업을보다 효율적으로 수행하려면 어떻게해야합니까? Javascript 에서이 작업을 수행하고 있으며 n은 일반적으로 30보다 작습니다. 동일하게 유지되면 문제가됩니까?


3
코드의 성능을 확인하기 위해 코드를 실행 해 보셨습니까?
thedaian

아니요, 저는 O (n ^ 2) 때문에 나쁘다고 가정하고 있습니다.
jcora

1
30 개의 물체 만? 공간 분할을 권장했지만 30 개의 객체 만 있으면 아무런 효과가 없습니다. 다른 사람들이 지적한 몇 가지 사소한 최적화가 있지만 모두 규모에 대한 사소한 최적화입니다.
존 맥도날드

답변:


16

최대 30 개의 객체 만 있으면 프레임 당 한 번 이상 동일한 두 쌍을 서로 비교하지 않는 것 외에는 최적화가 많이 필요하지 않습니다. 아래 코드 샘플 중 어느 것을 다룰 것인가? 그러나 물리 엔진이 사용할 다른 최적화에 관심이 있다면이 게시물의 나머지 부분을 계속 읽으십시오.

필요한 것은 Octree (3D 게임의 경우) 또는 Quadtree (2D 게임의 경우) 와 같은 공간 분할 구현 입니다. 이들은 세계를 하위 섹션으로 분할 한 다음 각 하위 섹션은 최소 크기로 세분 될 때까지 동일한 저택에서 추가로 분할됩니다. 이를 통해 다른 객체와 동일한 지역에있는 다른 객체를 매우 빠르게 확인할 수 있으므로 확인해야하는 충돌의 양이 제한됩니다.

공간 분할 외에도 각 물리 객체에 대해 AABB ( 축 정렬 경계 상자 )를 만드는 것이 좋습니다 . 이를 통해 한 객체의 AABB를 다른 객체와 비교할 수 있습니다. 이는 객체 간의 폴리 당 세부 검사보다 훨씬 빠릅니다.

이것은 복잡하거나 큰 물리 객체에 대해 한 단계 더 나아가 물리 메쉬 자체를 세분화하여 각 서브 셰이프에 자체 AABB를 제공하여 두 객체의 AABB가 겹치는 경우에만 확인할 수 있습니다.

대부분의 물리 엔진은 일단 물리 바디가 휴식을 취하면 물리 바디에서 활성 물리 시뮬레이션을 비활성화합니다. 피직스 바디가 비활성화되면 각 프레임마다 AABB와의 충돌 만 검사하면됩니다. AABB와 충돌하는 경우 다시 활성화되고보다 세밀한 충돌 검사가 수행됩니다. 시뮬레이션 시간이 줄어 듭니다.

또한 많은 물리 엔진은 '시뮬레이션 아일랜드 (simulation islands)'를 사용하는데, 여기에는 서로 가까운 물리 그룹이 함께 그룹화됩니다. 시뮬레이션 섬의 모든 것이 정지 상태이면 시뮬레이션 섬 자체가 비활성화됩니다. 시뮬레이션 섬의 장점은 섬이 비활성화되면 내부의 모든 몸체가 충돌 검사를 중지 할 수 있으며, 각 프레임에서 유일한 점검 사항은 섬의 AABB에 진입했는지 확인하는 것입니다. 일단 섬의 AABB에 무언가가 들어가면 섬 내의 각 몸체가 충돌을 확인해야합니다. 내부에있는 몸체가 스스로 다시 움직이기 시작하면 시뮬레이션 섬도 다시 활성화됩니다. 시체가 그룹의 중심에서 충분히 멀리 이동하면 섬에서 제거됩니다.

결국 당신은 (의사 코드로) 다음과 같은 것을 남깁니다.

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

또한 이와 같은 루프 내에 너무 많은 루프를 두지 않는 것이 좋습니다. 위의 샘플은 아이디어를 얻었으므로 위의 것과 같은 기능을 제공하는 여러 함수로 나누었습니다.

또한 AABBNodes 컨테이너를 반복하는 동안 충돌 검사를 놓칠 수 있으므로 AABBNodes 컨테이너를 변경하지 마십시오. 이것은 상식처럼 들릴지 모르지만 충돌에 반응하는 것이 예상치 못한 변화를 일으키는 것이 얼마나 쉬운 지 놀랄 것입니다. 예를 들어 충돌로 인해 충돌하는 객체 중 하나가 Octree 노드의 AABB에서 객체를 제거하기에 충분한 위치로 변경되면 해당 컨테이너가 변경 될 수 있습니다. 이 문제를 해결하려면 검사 중에 발생하는 모든 충돌 이벤트 목록을 유지 한 다음 모든 검사가 완료된 후 목록을 통해 실행하고 충돌 이벤트를 전송하는 것이 좋습니다.


4
기존 분석법에 독자의 마음을 열 수있는 훌륭하고 유용한 기술 정밀도로 일관된 답변을 제공합니다. +1
Valkea

충돌하는 객체를 제거해야하는 경우 어떻게합니까? 용기를 교체 할 수 있습니까? 더 이상 객체가 필요하지 않으므로 컨테이너에서 컨테이너를 제거한다는 의미입니다. "파기"되었기 때문입니다. 충돌 감지 중에 충돌을 제거하지 않으면 충돌 이벤트를 실행하기 위해 루프가 하나 더 필요합니다.
newguy

충돌하는 객체를 제거하는 것은 좋지만 전체 시뮬레이션에서 충돌 패스가 완료 될 때까지 기다릴 것을 권장합니다. 일반적으로 제거해야 할 오브젝트에 플래그를 지정하거나 제거 할 오브젝트 목록을 생성 한 다음 충돌 시뮬레이션이 완료된 후에 해당 변경 사항을 적용합니다.
Nic Foster

4

예제는 각 객체 쌍을 여러 번 테스트합니다.

0,1,2,3을 포함하는 배열로 매우 간단한 예를 들어 봅시다.

코드를 사용하면 다음을 얻을 수 있습니다.

  • 루프 0에서 1, 2 및 3에 대해 테스트
  • 루프 1에서 0, 2 및 3 ===> (0-1은 이미 테스트)에 대해 테스트합니다.
  • 루프 2에서 0, 1 및 3 ===> (0-2 / 1-2 이미 테스트)에 대해 테스트합니다.
  • 루프 3에서 0, 1 및 2 ===> (0-3 / 1-3 / 2-3 이미 테스트)에 대해 테스트합니다.

이제 다음 코드를 보자.

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

0,1,2,3을 포함하는 배열을 다시 한 번 사용하면 다음과 같은 동작이 발생합니다.

  • 루프 0에서 1, 2, 3에 대해 테스트
  • 루프 1에서 2, 3에 대해 테스트
  • 루프 2에서 3에 대해 테스트
  • 루프 3에서는 아무것도 테스트하지 않습니다.

두 번째 알고리즘은 6 번의 충돌 테스트를 받았으며, 이전 알고리즘은 12 번의 충돌 테스트를 요청했습니다.


이 알고리즘 N(N-1)/2은 여전히 ​​O (N ^ 2) 성능 인 비교를 수행합니다.
카이

1
요청에 따라 30 개의 객체가 있으면 870에 대한 465 충돌 테스트를 의미합니다. 또한, 다른 답변에서 제공되는 솔루션은 정확히 동일한 알고리즘입니다.)
Valkea

1
@ 발 케아 : 글쎄요. :)
Nic Foster

@NicFoster : 그렇습니다.;) 알고리즘의 파티셔닝 부분이 아니라 선택한 객체 간의 충돌 테스트에 대해 엄격하게 말하고 있습니다. 나는 그것을 쓰고 있었다.
Valkea

이것을 상각이라고합니까? 어쨌든 고마워!
jcora

3

필요에 따라 알고리즘을 설계하되 구현 세부 사항을 캡슐화하십시오. Javascript에서도 기본 OOP 개념이 적용됩니다.

의 경우는 N =~ 30, O(N*N)문제가되지 않습니다, 그리고 선형 검색이 밖으로 어떤 대안으로 단지 빨리 될 가능성이 높습니다. 그러나 코드에 가정을 하드 코딩하고 싶지 않습니다. 의사 코드에는 인터페이스가 있습니다.

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

항목 목록에서 수행 할 수있는 작업에 대해 설명합니다. 그런 다음이 인터페이스를 구현하는 ArrayContainer 클래스를 작성할 수 있습니다. Javascript에서 코드는 다음과 같습니다.

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

다음은 300 개의 경계 상자를 만들고 모든 교차점을 얻는 예제 코드입니다. ArrayContainer 및 QuadTreeContainer를 올바르게 구현 한 경우 코드에서 변경해야하는 것은로만 변경 var allMyObjects = new ArrayContainer()하는 것 var allMyObjects = QuadTreeContainer()입니다.

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

계속해서 표준 ArrayContainer에 대한 구현을 마무리했습니다.

http://jsfiddle.net/SKkN5/1/


참고 :이 답변은 코드베이스가 너무 커지고 지저분하며 관리하기가 어렵다는 Bane의 불만에 의해 동기 부여되었습니다. 배열과 트리를 사용하는 것에 대한 토론에별로 도움이되지는 않지만 코드를 더 잘 구성하는 방법에 대한 적절한 답변이되기를 바랍니다.
Jimmy

2

또한 눈에 띄게 충돌 할 수있는 것보다 객체 유형을 고려해야합니다.

예를 들어 플레이어는 자신의 총알을 제외한 모든 것과 충돌하는지 확인해야합니다. 그러나 적들은 플레이어의 총알에 대해서만 점검하면됩니다. 글 머리 기호는 서로 충돌 할 필요가 거의 없습니다.

이를 효율적으로 구현하려면 객체 유형별로 하나씩 별도의 객체 목록을 유지하려고합니다.

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