충돌에 대한 처리는 여러 단계에 걸쳐서 이루어진다.
먼저 충돌에 대한 정보를 얻는 방법이다.
1. 충돌 객체에 대한 리스트를 얻는 방법이다.
계 클래스, 강체 클래스에는 이러한 리스트를 얻어내는 방법이 존재한다.
for (b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext())
contact->... //do something with the contact
for (b2ContactEdge* edge = body->GetContactList(); edge; edge = edge->next)
edge->contact->... //do something with the contact
충돌 객체만 얻어내면 그곳에서 충돌한 고정체 정보, 충돌 위치 등의 정보를 얻어낼 수 있다. 나중에 설명한다.
2. 충돌 리스너를 이용한 방법이다.
Box2D에서는 충돌시, 적절한 시점에 호출되는 콜백 메서드를 4가지를 정의한다.
void BeginContact(b2Contact* contact);
void EndContact(b2Contact* contact);
void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);
void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
보면 알겠지만, 첫 인자로 충돌객체가 들어오며 이 객체를 이용하여 역시 충돌에 대한 정보를 얻어낼 수 있다.
b2Fixture* a = contact->GetFixtureA();
b2Fixture* b = contact->GetFixtureB();
이와 같이 충돌한 고정체가 무엇인지 파악할 수 있다. 그러나 중요한 것은 A, B로 표기된 쌍방의 고정체가 누구인지는 전혀 알 수 없다는 것이다. 공과 방망이가 충돌했을 때, A가 공, B가 방망이가 될 수 있고 그 반대가 될수도 있다는 것이다.
그렇다면 이를 구분하기 위한 방법은 없는 것인가. 있었다. 사용자 데이터를 이용한 방법이다. 이전 학습해봤던 내용에서 사용자 데이터를 넣을 수 있는 클래스 중, 고정체가 분명 그곳에 있었다.
따라서 공을 나타내는 고정체에 사용자 데이터로 공이라는 식별자를 넣은 뒤, 이 메서드에서 a 혹은 b 객체변수에서 사용자 정의 데이터를 얻어온 뒤 그 식별자가 공인지 방망이인지를 확인하고 처리할 수 있다.
충돌은 첫째로 AABB라 불리는 강체간의 사각형 영역 교차로부터 발생한다.
- 이 때는 절대 고정체끼리 겹치지 않는다.
- 충돌 객체는 존재하나, IsTouching() 메서드는 거짓을 반환한다.
고정체간 겹칩이 일어날 경우, 충돌이 실제 발생한 것으로 간주된다.
- BeginContact 콜백이 호출된다.
- IsTouching() 메서드가 참을 반환한다.
- 주의할 것은, 각 시연(스텝) 단계마다 강체는 그 시연(스텝) 단계 사이의 시간 간격만큼 이동된 위치에서 그려지게 된다. 이는 충돌이 예상되는 상황임에도 불구하고 물체가 충돌이 없어 관통을 하는 것과 같은 현상을 보여줄 수 있는 결과를 만들 수 있다. 가령 막대의 폭은 5cm이나 공은 한 시연(스텝) 단계마다 6cm를 이동한다면 막대 바로 앞 1cm 앞에 있던 공은 다음 시연(스텝) 단계에서 막대 바로 뒤에 위치하며, 고정체간 겹침은 이때 절대 발생하지 않는다.
- 이를 제어하기 위해서는 강체 객체에서 SetBullet(ture) 형태로 메서드를 호출하여 탄환의 종류임을 설정해 주거나 강체 정의 객체에서 bullet 속성의 값을 참으로 만든 뒤, 강체를 생성하여 탄환의 종류를 설정해 주어야 한다. 이 경우 충돌을 감지하기 위한 섬세한 작업이 수행되게 된다. 단, 그만큼 cpu를 사용하여 성능은 떨어지게 된다.
충돌점에 대한 개념 또한 존재한다. 이 점은 최대 2개이며, 그 이하일 수도 있다. 충돌이 일어난 경우 고정체간 겹치는 것을 방지하기 위한 방향으로 각각의 고정체에 충격량이 적용되게 되는데, 방향은 충돌 법선 벡터를 따른다.
이게 난점인데, 이해하기가 힘들다.
이 충돌 법선 벡터나 충돌점은 다음과 같이 구할 수 있다.
이 코드는 리스너나, 충돌 객체를 얻은 코드 뒷부분에 작성하면 된다.
//normal manifold contains all info...
int numPoints = contact->GetManifold()->pointCount;
//...world manifold is helpful for getting locations
b2WorldManifold worldManifold;
contact->GetWorldManifold( &worldManifold );
//단지 예제일 뿐, 뒤의 worldManifold.normal 이 충돌 법선 벡터를 가져온다.
//points는 배열로, 각 요소가 충돌점을 나타낸다.
float normalLength = 0.1f;
b2Vec2 normalStart = worldManifold.points[0] - normalLength * worldManifold.normal;
b2Vec2 normalEnd = worldManifold.points[0] + normalLength * worldManifold.normal;
위 정보를 이용해서 그 위치를 표기하면
//draw collision points
glBegin(GL_POINTS);
for (int i = 0; i < numPoints; i++)
glVertex2f(worldManifold.points[i].x, worldManifold.points[i].y);
glEnd();
glBegin(GL_LINES);
glColor3f(1,0,0);//red
glVertex2f( normalStart.x, normalStart.y );
glColor3f(0,1,0);//green
glVertex2f( normalEnd.x, normalEnd.y );
glEnd();
로 할 수 있다. 단 아래의 선을 긋는 부분에서는 일부러 그 위치를 0번 충돌점에서 한 것이다. 절대 위치가 있는 것이 아니다.
법선 벡터라 표현했으나, 절대 충돌 방향을 알려주는 척도가 되지는 않는다. 이는 불연속적인 화면 렌더링에 의한 충돌 감지 방식에 그 원인이 있다. 즉 충돌점은 실제 정확한 충돌 지점이 아니며, 이는 법선 벡터의 방향이 순간마다 달라질 수 있다는 것을 의미한다.
법선 벡터의 방향은 겹친 고정체들을 밀어냈을 때 가장 효율적으로 최단거리를 통해 겹치지 않도록 하는 방향으로 결정된다. 다음 시연(스텝) 단계에서 강체가 밀렸어도 아직 시간적으로 부족하여 겹쳐있는 부분이 남아있을 때가 있는데 이 때도 이 순간에 대한 가장 밀었을 때 짧게 벗어날 수 있는 바향으로 새로 설정되기 때문에 충돌 방향을 판단하는 기준으로는 매우 부적합하다.
다른 방식으로는 충돌 이후 속도를 이용해서 판단하는 방법인데, 아직 머리가 안되서 이해를 하지 못했다.
다시 돌아와서, 충돌이 일어났을 때(고정체가 겹쳤을 때) Box2D가 충돌을 처리하기 직전에 PreSolve 콜백이 호출된다. 여기서 충돌 전에 특정 처리를 할 수 있는 기회가 주어진다. 이후 Box2D의 충돌 처리 이후에는 PostSolve 콜백이 호출되며, 여기서는 충돌 이후의 정보에 대한 처리를 할 수 있다. 정보는 가해진 충격량이 얼마인지에 대한 것이 주를 이룬다. 2번째 인자로 이 크기를 구할 수 있다.
impulse->normalImpulses[0]; 여기서는 0을 이용했다.
* b2ContactImpulse 구조체는 3개의 멤버를 가진다. 첫째는 normalImpulses이고 둘째는 tangentImpulse이다. count는 셋째로 충돌점의 갯수와 같다. 첫째 인자는 각 충돌점에 가해진 충격량이며, 둘째는 적용된 힘의 방향이다. 셋째는 충돌점의 갯수와 같다. 이는 b2Manifold의 각각의 충돌점과 일대일로 대응되는 개념이다.
다음으로 고정체는 겹치지 않고 다시 AABBs 라 불리는 부분만 겹치게 되는 상황이 오면 EndContact 콜백이 호출된다. IsTouching() 메서드는 다시 거짓을 반환한다.
이 영역마저 겹치지 않는다면, 충돌은 끝나고 생성되었던 충돌 객체들은 충돌 리스트에서 제거된다.
- PreSolve와 PostSolve에서 설정할 수 있는 몇가지 정보.
void SetEnabled(bool flag);//non-persistent - need to set every time step 지속적이지 않으므로 매번 설정해 주어야 한다. 이것을 설정하면 충돌은 무시된다. : 관통된다.
//these available from v2.2.1
void SetFriction(float32 friction);//persists for duration of contact 충돌간 지속된다.
void SetRestitution(float32 restitution);//persists for duration of contact 충돌간 지속된다.
내가 나중에 참고하기 위한 글로 작성하는 글들을 내가 보고도 이해가 안된다.. 나중에 다시 정리해야겠다.
댓글 없음:
댓글 쓰기