Lexa

WPF, C#, Objective C and a little Math

Box2D DebugDraw (b2Draw implementation) for WINAPI, WINGDI

Quite often I create simple or test apps using native Win32 project (WINAPI). This time I wanted an output of Box2D world. No ready-to-copypaste implementation was found in the Net, so I had to make my own, and now I can share it :)

And here is the source code:

Header file

// DebugDrawGDI.h
// 
// 
// Created by PavlAl on 06.05.2013
// Copyright (c) 2013 tatalata, http://talatala.com
// 

#include <Box2D/Box2D.h>

#pragma once

class DebugDrawGDI : public b2Draw
{
public:
	DebugDrawGDI();
	~DebugDrawGDI();

	/// Initialize drawing of a whole world.
	void Begin(RECT & winRect, HDC hdc, b2World *world);

	/// Initialize drwaing in a certain AABB.
	void Begin(RECT & winRect, HDC hdc, b2World *world, const b2AABB & aabb);

	/// Unintialize drawing.
	void End();

	/// Draw a closed polygon provided in CCW order.
	virtual void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

	/// Draw a solid closed polygon provided in CCW order.
	virtual void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);

	/// Draw a circle.
	virtual void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color);

	/// Draw a solid circle.
	virtual void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color);

	/// Draw a line segment.
	virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color);

	/// Draw a transform. Choose your own length scale.
	/// @param xf a transform.
	virtual void DrawTransform(const b2Transform& xf);

private:
	/// Setup current brush and pen.
	void SetColor(const b2Color& color);
	/// Delete current brush and pen.
	void CleanupGDIObjects();

private:
	/// Reference to a HDC set in Begin method. 
	HDC m_hdc;
	/// Last used brush, owned by DebugDrawGDI.
	HBRUSH m_brush;
	/// Last used pen, DebugDrawGDI.
	HPEN m_pen;
	/// Last used color.
	DWORD m_brushColor;
};

Implementation file

// DebugDrawGDI.cpp
// 
// 
// Created by PavlAl on 06.05.2013
// Copyright (c) 2013 tatalata, http://talatala.com
// 
#include "stdafx.h"
#include "DebugDrawGDI.h"
#include <vector>

#define DEBUG_DRAW_MAX_WORLD_SIZE 1000
#define SCALE 1000.f
#define UP(v) ((v) * SCALE)
#define UPi(v) ((int)UP(v))

class AABBDetector : public b2QueryCallback
{
public:
	b2AABB aabb;

	AABBDetector()
	{
		aabb.lowerBound = b2Vec2(DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE);
		aabb.upperBound = b2Vec2(-DEBUG_DRAW_MAX_WORLD_SIZE, -DEBUG_DRAW_MAX_WORLD_SIZE);
	}

	bool ReportFixture(b2Fixture* fixture)
	{
		b2Vec2 pos = fixture->GetBody()->GetPosition();

		if (aabb.lowerBound.x > pos.x) aabb.lowerBound.x = pos.x;
		if (aabb.upperBound.x < pos.x) aabb.upperBound.x = pos.x;
		if (aabb.lowerBound.y > pos.y) aabb.lowerBound.y = pos.y;
		if (aabb.upperBound.y < pos.y) aabb.upperBound.y = pos.y;

		return true;
	}
};

namespace
{
	b2AABB CreateAABB(float xMin, float yMin, float xMax, float yMax)
	{
		b2AABB aabb;
		aabb.lowerBound = b2Vec2(xMin, yMin);
		aabb.upperBound = b2Vec2(xMax, yMax);
		return aabb;
	}
}

DebugDrawGDI::DebugDrawGDI()
: m_hdc(NULL)
, m_brush(NULL)
, m_pen(NULL)
, m_brushColor(0)
{
}

DebugDrawGDI::~DebugDrawGDI()
{
	CleanupGDIObjects();
}

void DebugDrawGDI::Begin(RECT & winRect, HDC hdc, b2World *world, const b2AABB & aabb)
{
	m_hdc = hdc;
	world->SetDebugDraw(this);

	BOOL bOK = SetGraphicsMode(hdc, GM_ADVANCED);
	ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);

	// Cleanup area.
	FillRect(hdc, &winRect, (HBRUSH)GetStockObject(WHITE_BRUSH));

	// Change world transform.
	int outputWidth = winRect.right - winRect.left;
	int outputHeight = winRect.bottom - winRect.top;
	float boundsWidth = UP(aabb.upperBound.x - aabb.lowerBound.x);
	float boundsHeight = UP(aabb.upperBound.y - aabb.lowerBound.y);
	float cx = outputWidth / boundsWidth;
	float cy = outputHeight / boundsHeight;
	float c = cx > cy ? cy : cx;

	XFORM x;
	x.eM11 = c;
	x.eM21 = 0;
	x.eDx = winRect.left + (outputWidth - boundsWidth * c) / 2 - UP(aabb.lowerBound.x) * c;
	x.eM12 = 0;
	x.eM22 = -c;
	x.eDy = winRect.top + (outputHeight - boundsHeight * c) / 2 + UP(aabb.upperBound.y) * c;

	SetWorldTransform(hdc, &x);
}

void DebugDrawGDI::Begin(RECT & winRect, HDC hdc, b2World *world)
{
	// Get world AABB.
	AABBDetector d;
	world->QueryAABB(&d, CreateAABB(-DEBUG_DRAW_MAX_WORLD_SIZE, -DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE, DEBUG_DRAW_MAX_WORLD_SIZE));

	d.aabb.lowerBound -= b2Vec2(1, 1);
	d.aabb.upperBound += b2Vec2(1, 1);

	Begin(winRect, hdc, world, d.aabb);
}

void DebugDrawGDI::End()
{
	ModifyWorldTransform(m_hdc, NULL, MWT_IDENTITY);
	m_hdc = NULL;
}

void DebugDrawGDI::CleanupGDIObjects()
{
	if (m_brush) ::DeleteObject(m_brush);
	if (m_pen) ::DeleteObject(m_pen);

	m_brush = NULL;
	m_pen = NULL;
}

void DebugDrawGDI::SetColor(const b2Color& color)
{
	DWORD rgb = RGB(color.r * 255, color.g*255, color.b*255);
	if (!m_brush || !m_pen || m_brushColor != rgb)
	{
		CleanupGDIObjects();

		m_brush = ::CreateSolidBrush(rgb);
		m_pen = ::CreatePen(PS_SOLID, 1, RGB(color.r * 155, color.g*155, color.b*155));
		m_brushColor = rgb;
	}

	::SelectObject(m_hdc, m_brush);
	::SelectObject(m_hdc, m_pen);
}

/// Draw a closed polygon provided in CCW order.
void DebugDrawGDI::DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
    static std::vector<POINT> pts;
    pts.resize(vertexCount + 1);
    for (int i = 0; i < vertexCount; ++i, ++vertices)
    {
        POINT p;
        p.x = UPi(vertices->x);
        p.y = UPi(vertices->y);
        pts[i] = p;
    }
    pts[vertexCount] = pts.front();
    
    SetColor(color);
    ::Polyline(m_hdc, &pts[0], vertexCount + 1);
}

/// Draw a solid closed polygon provided in CCW order.
void DebugDrawGDI::DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
    static std::vector<POINT> pts;
    pts.resize(vertexCount + 1);
    for (int i = 0; i < vertexCount; ++i, ++vertices)
    {
        POINT p;
        p.x = UPi(vertices->x);
        p.y = UPi(vertices->y);
        pts[i] = p;
    }
    pts[vertexCount] = pts.front();
    
    SetColor(color);
    ::Polygon(m_hdc, &pts[0], vertexCount + 1);
}

/// Draw a circle.
void DebugDrawGDI::DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color)
{
	SetColor(color);

	// Cleanup fill brush.
	::SelectObject(m_hdc, GetStockObject(NULL_BRUSH));

	::Ellipse(
		m_hdc, 
		UPi(center.x - radius),
		UPi(center.y - radius),
		UPi(center.x + radius),
		UPi(center.y + radius));

	SetColor(color);
}

/// Draw a solid circle.
void DebugDrawGDI::DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color)
{
	SetColor(color);

	::Ellipse(
		m_hdc, 
		UPi(center.x - radius),
		UPi(center.y - radius),
		UPi(center.x + radius),
		UPi(center.y + radius));

	DrawSegment(center, center + b2Vec2(axis.x * radius, axis.y *radius), color);
}

/// Draw a line segment.
void DebugDrawGDI::DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color)
{
	SetColor(color);

	::MoveToEx(m_hdc, UPi(p1.x), UPi(p1.y), NULL);
	::LineTo(m_hdc, UPi(p2.x), UPi(p2.y));
}

/// Draw a transform. Choose your own length scale.
/// @param xf a transform.
void DebugDrawGDI::DrawTransform(const b2Transform& xf)
{
	const float length = 1;
	b2Vec2 start = xf.p;
	b2Vec2 axis = xf.q.GetXAxis();
	b2Vec2 end = start + b2Vec2(axis.x * length, axis.y * length);
	::MoveToEx(m_hdc, UPi(start.x), UPi(start.y), NULL);
	::LineTo(m_hdc, UPi(end.x), UPi(end.y));
}

Categorised as: Box2D, Games, WINAPI


Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>