Saturday, November 17, 2012

Universal Box2D debug draw for OpenGL ES 1.x and OpenGL ES 2.0



Previous parts

 After introduction in "Simple cross-platform game engine - Introduction" there is first of the snippets here. This time it is on implementing debug draw class for great Box2D physics library.

The problem

 I am very new to Box2D, but the library is very well documented and easy to use. I implemented it into Android NDK and Win platforms of my engine. I tried to create simple world with two objects and two edges but then I found that I am missing some class to draw it on screen without textures and all the stuff around.
 First I looked into packed source that comes with whole Box2D package and found class DebugDraw in Render.h/.cpp in Testbed\Framework directory. Unfortunately this class is using "big" OpenGL with calls that are not supported in "small" OpenGL ES.
 After some searching I found iPhone implementation. Class GLESDebugDraw supports only OpenGL ES 1.x while in my engine I can switch in parameters whether to use OpenGL ES 1.x or 2.0.
 As I did not find any implementation for 2.0 I had to write one by myself.

Class b2Draw

 Box2D has class b2Draw in its Common directory. This class contains a bunch of virtual methods that waits for your implementation. The following implementation is bind to my engine but very loosely so you can take it and with small adjustments it will work for you too. I will mention these bindings during the text.

 Here goes the header:

1:  /*  
2:   * Box2DDebugDraw.h  
3:   *  
4:   * Created on: 16.11.2012  
5:   *   Author: Tom  
6:   */  
7:    
8:  #ifndef BOX2DDEBUGDRAW_H_  
9:  #define BOX2DDEBUGDRAW_H_  
10:    
11:  #include "../../System/system.h"  
12:  #ifdef SBC_DRAW_BOX2D_DEBUG  
13:    
14:  #include <Box2D/Box2D.h>  
15:  #if (SBC_USE_OPENGL_VERSION == 2)  
16:  #include "../glesProgram.h"  
17:  #endif  
18:    
19:  struct b2AABB;  
20:    
21:  namespace SBC  
22:  {  
23:  namespace Engine  
24:  {  
25:    
26:  class Box2DDebugDraw : public b2Draw  
27:  {  
28:  private:  
29:       enum  
30:       {  
31:            eTriangles = 0x01,  
32:            eLines   = 0x02,  
33:            ePoints  = 0x04  
34:       };  
35:    
36:  public:  
37:       static const u32 MAX_VERTICES = 64;  
38:       static const u32 CIRCLE_SEGMENTS = 16;  
39:    
40:  public:  
41:       Box2DDebugDraw(float aRatio);  
42:       ~Box2DDebugDraw();  
43:       void construct();  
44:    
45:  public:  
46:       void DrawPolygon(const b2Vec2* aVertices, int32 aVertexCount, const b2Color& aColor);  
47:       void DrawSolidPolygon(const b2Vec2* aVertices, int32 aVertexCount, const b2Color& aColor);  
48:       void DrawCircle(const b2Vec2& aCenter, float32 aRadius, const b2Color& aColor);  
49:       void DrawSolidCircle(const b2Vec2& aCenter, float32 aRadius, const b2Vec2& aAxis, const b2Color& aColor);  
50:       void DrawSegment(const b2Vec2& aP1, const b2Vec2& aP2, const b2Color& aColor);  
51:       void DrawTransform(const b2Transform& aXf);  
52:       void DrawPoint(const b2Vec2& aP, float32 aSize, const b2Color& aColor);  
53:       void DrawString(int aX, int aY, const u8* aString, ...);  
54:       void DrawAABB(b2AABB* aAabb, const b2Color& aColor);  
55:    
56:  private:  
57:       void createPolygonVertices(const b2Vec2* aVertices, int32 aVertexCount);  
58:       void createCircleVertices(const b2Vec2& aCenter, float32 aRadius);  
59:       void drawPrimitives(u32 aPrimitiveTypes, u32 aCount, const b2Color& aColor);  
60:    
61:  private:  
62:       f32 mRatio;  
63:       SBC::System::MathUtils::fvec2 mVertices[MAX_VERTICES];  
64:       f32 mPointSize;  
65:    
66:  #if (SBC_USE_OPENGL_VERSION == 2)  
67:       SBC::Engine::glesProgram* mProgram;  
68:       GLuint mColorLocation;  
69:       GLuint mPointSizeLocation;  
70:       GLuint mPositionLocation;  
71:  #endif  
72:  };  
73:    
74:  } /* namespace Engine */  
75:  } /* namespace SBC */  
76:    
77:  #endif // SBC_DRAW_BOX2D_DEBUG  
78:  #endif /* BOX2DDEBUGDRAW_H_ */  
79:    

 The class is easy to understand the "my-engine-specifics" you will have to change are:
  • line11: #include "../../System/system.h" - this defines MathUtils and template class fvec2 used later for storing 2D vectors. It also defines which version of OpenGL ES to use,
  • line 12: #ifdef SBC_DRAW_BOX2D_DEBUG - if defined the Box2D debug is requested and the class will get compiled,
  • line 15-17: #include "../glesProgram.h" - engine class for loading and compiling shaders is included. Having it here makes the life easier but it can be replaced with direct GL calls in implementation,
  • line 21-24: debug draw is part of SBC::Engine namespace
  • line 63: SBC::System::MathUtils::fvec2 mVertices[MAX_VERTICES]; - here is use of template vector class. But any 2D vector class that has x and y members will do the job,
  • lines 66-71: pointer to class wrapping OpenGL program and cached locations of some of the uniforms and attributes.

 

Implementation

 You can see the whole implementation in attached .zip file. As the header contains three private helper methods at lines 57-59 the implementation of particular Draw... methods is then as simple as this:

1:  //------------------------------------------------------------------------  
2:  void Box2DDebugDraw::DrawSolidPolygon(const b2Vec2* aVertices, int32 aVertexCount, const b2Color& aColor)  
3:  {  
4:       createPolygonVertices(aVertices, aVertexCount);  
5:       drawPrimitives(eTriangles + eLines, aVertexCount, aColor);  
6:  }  

 This works well for both OpenGL ES 1.x and 2.0. For OpenGL 2.0 there is simple program created when initializing the class with vertex and fragment shaders like this:

1:  VERTEX SHADER:  
2:  -----------------------------  
3:    
4:  uniform mediump mat4 u_projection;  
5:  uniform mediump float u_pointSize;  
6:  attribute vec2 a_position;  
7:    
8:  void main()  
9:  {  
10:    gl_PointSize = u_pointSize;  
11:    vec4 position = vec4(a_position, 0.0, 1.0);  
12:    gl_Position = u_projection * position;  
13:  }  
14:    
15:    
16:  FRAGMENT SHADER  
17:  ----------------------------------  
18:    
19:  precision mediump float;  
20:  uniform vec4 u_color;  
21:    
22:  void main()   
23:  {   
24:    gl_FragColor = u_color;  
25:  }

 The main work is done in private drawPrimitives method, so I will it put here:

1:  //------------------------------------------------------------------------  
2:  void Box2DDebugDraw::drawPrimitives(u32 aPrimitiveTypes, u32 aCount, const b2Color& aColor)  
3:  {  
4:  #if (SBC_USE_OPENGL_VERSION == 1)  
5:       glVertexPointer(2, GL_FLOAT, 0, mVertices);  
6:    
7:       if (aPrimitiveTypes & eTriangles)  
8:       {  
9:            glColor4f(aColor.r, aColor.g, aColor.b, 0.5f);  
10:            glDrawArrays(GL_TRIANGLE_FAN, 0, aCount);  
11:       }  
12:    
13:       if (aPrimitiveTypes & eLines)  
14:       {  
15:            glColor4f(aColor.r, aColor.g, aColor.b, 1.0f);  
16:            glDrawArrays(GL_LINE_LOOP, 0, aCount);  
17:       }  
18:    
19:       if (aPrimitiveTypes & ePoints)  
20:       {  
21:            glColor4f(aColor.r, aColor.g, aColor.b, 1.0f);  
22:            glPointSize(mPointSize);  
23:            glDrawArrays(GL_POINTS, 0, aCount);  
24:            glPointSize(1.0f);  
25:       }  
26:    
27:  #elif (SBC_USE_OPENGL_VERSION == 2)  
28:       glUseProgram(mProgram->getID());  
29:       glEnableVertexAttribArray(mPositionLocation);  
30:       glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, (GLfloat*) mVertices);  
31:    
32:       if (aPrimitiveTypes & eTriangles)  
33:       {  
34:            glUniform4f(mColorLocation, aColor.r, aColor.g, aColor.b, 0.5f);  
35:            glDrawArrays(GL_TRIANGLE_FAN, 0, aCount);  
36:       }  
37:    
38:       if (aPrimitiveTypes & eLines)  
39:       {  
40:            glUniform4f(mColorLocation, aColor.r, aColor.g, aColor.b, 1.0f);  
41:            glDrawArrays(GL_LINE_LOOP, 0, aCount);  
42:       }  
43:    
44:       if (aPrimitiveTypes & ePoints)  
45:       {  
46:            glUniform4f(mColorLocation, aColor.r, aColor.g, aColor.b, 1.0f);  
47:            glUniform1f(mPointSizeLocation, mPointSize);  
48:            glDrawArrays(GL_POINTS, 0, aCount);  
49:       }  
50:    
51:       glDisableVertexAttribArray(mPositionLocation);  
52:       glUseProgram(0);  
53:  #endif  

 As you can see the implementation depends on chosen OpenGL ES version.

 The result

 In your code use the class like this:

1:       debugDraw = new SBC::Engine::Box2DDebugDraw(SBC_BOX2D_PTM_RATIO);  
2:       debugDraw->construct();  
3:       world->SetDebugDraw(debugDraw);  
4:       debugDraw->SetFlags(b2Draw::e_shapeBit /* + b2Draw::e_jointBit + b2Draw::e_aabbBit +  
5:                 b2Draw::e_pairBit + b2Draw::e_centerOfMassBit*/);  

 
 If everything went ok, you should get something similar to picture bellow and the best thing is it works for both OpenGL ES versions just with simple define switch.

 
 In attached file here you will find whole implementation and also source for referred classes (fvec2, glesProgram). So it should be enough help for you to implement your own drawing class.