2014년 2월 28일 금요일

[일기]box2D. DebugDraw

Testbed에서나 Box2D에서나 DebugDraw 클래스를 제공한다.

 실제로 화면에 그래픽을 그려주는 역할을 한다.

 그런데 보통 Box2D를 통해 작성한 프로그램은 정의한 강체나 고정체의 좌표값들을 얻어와 사용자가 그게 무엇인지 정한 대로 벽이면 벽돌을 공이면 축구공의 이미지 같은 것을 그 얻은 좌표에 그려서 게임이면 게임, 테스트면 테스트로 프로그램을 만들게 된다. 즉 그리는 것은 사용자에게 모든 권한과 책임이 있다(사용자가 그려야 한다).

 그러나

 사용자가 직접 렌더링을 수행할 때, 그 위치조절 실패나 실수가 있을 수 있다. 그것으로 인하여 실제 프로그램 구동해 보면 화면에 보여지는 물체의 움직임은 물리적으로 볼 때 사실적으로 보이지 않을지도 모른다. 가령 실수로 덤블링 체조에서 덤블링은 화면 중앙에 두고 그 위에 튀어오르는 사람의 x위치와 y위치를 바꿔 사용했다고 하면, 프로그램이 모사하는 것은 실세계가 아닌 판타지 세계가 된다. 사람은 공중에서 덤블링을 튕기며 가로로 점프(덤블링)을 수행한다.

 그러나 판타지 세계를 모사하려는 것이 아니므로 이 때, DebugDraw를 이용하여 화면에 렌더링을 수행해 본다면, 이 클래스가 그리는 것은 정확성(위치 렌더링)을 보장할 수 있으므로, 사용자 자신이 실수한 렌더링 부분이 무엇인지를 판단하는데 도움을 받을 수 있다.

 현재 관련된 클래스의 가장 상위 추상클래스는 b2Draw이며, 이를 구현해 놓은 것이 DebugDraw 클래스이다. 이 두개 클래스 중 어느것을 선택하든 상관은 없으나, 후자를 선택해서 새로 정의해 보았다.

class FooDraw : public DebugDraw
{
public:
void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) {}
void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) {
    GLfloat glverts[16];
    glVertexPointer(2, GL_FLOAT, 0, glverts);
    glEnableClientState(GL_VERTEX_ARRAY);
    
    for (int i = 0; i < vertexCount; i++) {
      glverts[i*2]   = vertices[i].x;
      glverts[i*2+1] = vertices[i].y;
    }
    glColor4f( color.r, color.g, color.b, 0.1);
    glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount);
  
    glLineWidth(3);
    glColor4f( 1, 1, 0.97, 1 );
    glDrawArrays(GL_LINE_LOOP, 0, vertexCount);
}
void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color) {}
void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color) {}
void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) {}
void DrawTransform(const b2Transform& xf) {}
};

두번째 메서드만 구현되어 있다. 이를 Testbed 프레임워크 안에서 동작하도록 하려면(물론 자신의 프로젝트에서도 마찬가지이다) 계 클래스에 디버그드로우 객체를 지정해 주어야 한다.

m_world->SetDebugDraw(&fooDraw);

여기서 또한 콜백 받을 플래그를 지정해 주어야 한다.

fooDraw.SetFlags( b2Draw::e_shapeBit );

플래그는 다섯 가지가 있고 한꺼번에 지정이 가능하다.

e_shapeBit ( draw shapes )
e_jointBit ( draw joint connections )
e_aabbBit ( draw axis aligned bounding boxes )
e_pairBit ( draw broad-phase pairs )
e_centerOfMassBit ( draw a marker at body CoM )  

//출처 : http://www.iforce2d.net/b2dtut/debug-draw


실제 b2Draw 클래스에서는 다음과 같은 상수이다.

enum
{
e_shapeBit = 0x0001, ///< draw shapes
e_jointBit = 0x0002, ///< draw joint connections
e_aabbBit = 0x0004, ///< draw axis aligned bounding boxes
e_pairBit = 0x0008, ///< draw broad-phase pairs
e_centerOfMassBit = 0x0010 ///< draw center of mass frame
};


Test 클래스에서는 다음과 같이 플래그를 설정하고 있다.

uint32 flags = 0;
flags += settings->drawShapes * b2Draw::e_shapeBit;
flags += settings->drawJoints * b2Draw::e_jointBit;
flags += settings->drawAABBs * b2Draw::e_aabbBit;
flags += settings->drawCOMs * b2Draw::e_centerOfMassBit;
m_debugDraw.SetFlags(flags);

여기서 settings 포인터의 맴버값에 곱하기를 하고 있는데, 추측상 Testbed를 실행시킬때 존재하는 우측의 체크박스들에 대한 값들일 것이다. 체크되어 있다면 0이 아닌 값을 가지며 체크가 해제되어 있다면 0의 값을 가지게 될 것이다.

 곱해서 0이 된다면 플래그 설정이 되어 있지 않다고 볼 수 있다.

그 다음 해주어야 할 작업은 다음과 같다.

 m_world->DrawDebugData();

 Test 클래스에서는 매 시연(스텝) 단계마다 이 메서드를 호출하므로 별도로 설정할 필요는 없다. 다만, Testbed 프레임워크가 아닌 자신 프로젝트에서 이와 같은 방식으로 DebugDraw를 사용할 경우 이를 호출해 주는 것을 잊으면 안된다.

DebugDraw의 하위 클래스 구현내용은 자유롭게 가능하다. 여기서 자유롭게는 어떠한 렌더링 API를 써도 무방하다는 것이다.

 OpenGL를 사용하든 OpenGL ES를 사용하든, DirectX를 사용하든 WINAPI를 이용하든 등등등등등...

이상.

댓글 없음:

댓글 쓰기