Introduction
Click here for the GDI+ version.
Box2d is a 2D physics engine. It realistically models the interactions between moving and colliding rigid bodies in two dimensions. Box2D does the math required to depict boxes, balls, and polygons moving, colliding, and bouncing across a virtual world. But Box2D doesn't draw anything. To turn this information into a game you have get information from Box2d, map it to coordinates useful for drawing, and draw your world.
Box2D uses floating point math and defines objects in MKS: Meters, Kilograms, Seconds. It is meant for objects from about a few centimeters in size up to a few meters.
It isn't really meant to model aircraft carriers or molecules. This means you will create a world in Box2d, set it in motion, then scale and translate (transform)
the Box2D information to information suitable for using to draw a game.
A very useful tool Box2D has is the DebugDraw facility. By proving a handful of simple drawing routines
to box2D you can quickly get a prototype up and running that shows Box2D objects as simple vector drawings. DebugDraw is also handy to see exactly what is going on inside Box2D.
Even if you have a fancy graphics front end it could be handy to turn on DebugDraw for debugging.
This project shows how to use set up a very simple box2d world,
and use DebugDraw to view this world in a bare bones win32 DirectX 2D program. For an excellent Box2D tutorial click here.
Setup
The first thing to do is download and compile the Box2D library from http://box2d.org/. For our project you will want
to make a couple of changes to the Properties of the Box2D project before compiling:
- BOX2D
- Configuration Properties
- General
- Character set: Use Unicode character set
These settings assume you are going to link Box2D with a win32 project using Unicode. It is very important to have the character set options match or you will get LOTS of linker errors.
Now we can start our project. Fire up visual studio and create a new Win32 Application project (or download mine). Choose C++/Win32/Win32 Project. Be sure the set the Project Name. First thing to do is tell our project we are using the Box2d.lib:
- Open the project configuration properties
- Check that our new project default to using
- Open the Linker section
- Click on input
- Additional Dependancies"
- Add Box2d.lib (don't add a path)
- Open the VC++ directories
- Edit "Include Directories"
- Add the Box2D library path, for example: C:\Users\somename\Documents\Visual Studio 20xx\Projects\box2d
- Edit "Library directories"
- Add the Box2D path, for example: C:\Users\somename\Documents\Visual Studio 2010\Projects\box2d\Build\vs20xx\bin\Debug
Note you have to add the debug version to your debug configuration, and the release version to your release version.
That takes care of the Project configuration. Next we have to add the DirectX 2D required headers. At the bottom of stdafx.h add:
#include <d2d1.h>
#include <Box2D/Box2D.h>
In your main cpp file add the following to tell the compiler you are using DirectX 2D:
#pragma comment(lib, "d2d1")
That takes care of the housekeeping.
The Code
Now onto the code. To implement Box2D DebugDraw
you have to implement a class based on the Box2d class b2draw. Here is the minimum that has to be implemented:
class DebugDrawGDI : public b2Draw
{
public:
DebugDrawGDI();
~DebugDrawGDI();
virtual void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);
virtual void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);
virtual void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color);
virtual void DrawSolidCircle(const b2Vec2& center, float32 radius,
const b2Vec2& axis, const b2Color& color);
virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color);
virtual void DrawTransform(const b2Transform& xf);
};
By implementing these six drawing functions Box2D can draw any world, in a simple debug drawing mode. So all we have to do is code up these six functions
in DirectX 2D. Writing these six functions is straightforward; the wrinkle is that Box2D is going to be sending in box2D world coordinates, and we have to convert
them to windows pixel coordinates. Most of our code is going to be devoted to setting up the transform that will allow DirectX 2D to correctly draw the Box2D data.
Box2D coordinates are in meters, x increases from left to right, and y increases as you go up. DirectX 2D coordinates are in pixels, x increases from left to right,
but y increases as you go down.
The technique we will use is to get the size of the DirectX 2D window, the size of the Box2D world, and setup a transform matrix we can pass on
to the renderTarget->SetTransform ()
function. Once we do this the transform handles all the coordinate mapping, and writing the six graphics becomes trivial.
Getting the win32 window size requires the HWND
of the main window. To make life easier I add a global variable to save it in:
HWND hWndGlobal;
Then in the InitInstance()
add this just before the call to ShowWindow()
:
hWndGlobal=hWnd;
Now we get the window size in the _twinMain() by:
RECT rect;
GetClientRect(hWndGlobal, &rect);
Getting the BoxD2 world size takes a bit more work. First, you have to create a Box2D world, add some
"bodies" to it, then query the "world" to see how big it is. For testing I make a world
with rectangle as ground, roof, and walls, and add a couple of dynamic objects inside these walls. Once we have created the
world, we can call a few Box2d functions to iterate over all the bodies and get the world size:
void DebugDrawGDI::GetBoundBox2DBounds(RECT *w, b2World *world)
{
b2Body *b;
b2Fixture *fix;
b2AABB bound;
float minX, maxX, minY, maxY;
minX=minY=1000000.0;
maxX=maxY=-1000000.0;
b=world->GetBodyList();
while ( b )
{
fix=b->GetFixtureList();
while ( fix )
{
bound=fix->GetAABB(0);
if ( bound.lowerBound.x < minX )
minX=bound.lowerBound.x;
if ( bound.upperBound.x > maxX )
maxX=bound.upperBound.x;
if ( bound.lowerBound.y < minY )
minY=bound.lowerBound.y;
if ( bound.upperBound.y > maxY )
maxY=bound.upperBound.y;
fix=fix->GetNext();
}
b=b->GetNext();
}
maxX+=2.0;
maxY+=2.0;
minX-=2.0;
minY-=2.0;
w->left=(long )minX;
w->right=(long )maxX;
w->top=(long )maxY;
w->bottom=(long )minY;
}
Now that we know how big everything is we can calculate our transform:
void DebugDrawGDI::GetBoundBox2DBounds(RECT *w, b2World *world)
{
b2Body *b;
b2Fixture *fix;
b2AABB bound;
float minX, maxX, minY, maxY;
minX=minY=1000000.0;
maxX=maxY=-1000000.0;
b=world->GetBodyList();
while ( b )
{
fix=b->GetFixtureList();
while ( fix )
{
bound=fix->GetAABB(0);
if ( bound.lowerBound.x < minX )
minX=bound.lowerBound.x;
if ( bound.upperBound.x > maxX )
maxX=bound.upperBound.x;
if ( bound.lowerBound.y < minY )
minY=bound.lowerBound.y;
if ( bound.upperBound.y > maxY )
maxY=bound.upperBound.y;
fix=fix->GetNext();
}
b=b->GetNext();
}
maxX+=2.0;
maxY+=2.0;
minX-=2.0;
minY-=2.0;
w->left=(long )minX;
w->right=(long )maxX;
w->top=(long )maxY;
w->bottom=(long )minY;
}
Then in our main drawing loop we just have to set the transform, something like this:
renderTarget->SetTransform(matrixTransform);
Where renderTarget
is a pointer to our current DirectX 2D render factory object. With the transform in place our drawing functions look like this:
void DebugDrawGDI::DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
int i;
ID2D1PathGeometry *geo;
ID2D1GeometrySink *sink;
ID2D1SolidColorBrush *brush;
D2D1::ColorF dColor(color.r, color.g, color.b);
D2D1_POINT_2F *points=new D2D1_POINT_2F [vertexCount+1];
HRESULT hr;
hr=factory->CreatePathGeometry(&geo);
hr=geo->Open(&sink);
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
sink->BeginFigure(D2D1::Point2F(vertices[0].x, vertices[0].y), D2D1_FIGURE_BEGIN_FILLED);
vertices++;
vertexCount--;
for (i = 0; i < vertexCount; i++, vertices++)
{
points[i].x = vertices->x;
points[i].y = vertices->y;
}
points[vertexCount].x = points[0].x;
points[vertexCount].y = points[0].y;
sink->AddLines(points, vertexCount);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
sink->Close();
SafeRelease(&sink);
renderTarget->CreateSolidColorBrush(dColor, &brush);
renderTarget->FillGeometry(geo, brush);
delete points;
SafeRelease(&geo);
}
Note that we do no scaling or mapping in the drawing function. The transform takes care of it all for us.
All that is left is to setup some animation and timing animation code. We have to change the default message loop so it doesn't just wait for messages.
Not much animation happens if your drawing code only gets called when a key is pressed. Use this code to process messages:
PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE);
while (msg.message!=WM_QUIT)
{
if (PeekMessage( &msg, NULL, 0, 0, PM_REMOVE))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
if ( ajrMain.MainLogic() )
{
ajrMain.MainDraw();
}
}
}
ajrMain.MainLogic()
does all the moving and timing of objects, and
ajMain.MainDraw()
draws the current world.
I created a new class to handle all the DirectX 2D stuff, timing, and logic. I added this, just above the message loop to create and initialize the object:
CAjrMain ajrMain(&rect, hWndGlobal); ajrMain.CreateBox2dWorld();
CAjrMain has code to initialize DirectX 2D, load a bitmap, do the simple game logic and timing. I put it all in a separate cpp file. I used the
QueryPerformanceCounter();
to time the animation.
As well, the code to create the Box2D world, an instance of the DebugDraw
class, etc… goes into the
CAjrMain
class.
See the GDI+ version (link at top of article) for some simple collision detection code. Box2d has very good collision detection support using callback function.
History