ぬうぱんの備忘録

技術系のメモとかいろいろ

作った曲一覧はこちら

SDL+OpenGL+Box2Dでデバッグドロー付きHelloWorld

この記事は

 SDL+OpenGLのゲームにBox2D組み込むのにいろいろいじって勉強しようと思ったけど、ソースコード一個にまとまったごくごく簡単な描画付きサンプルないよなー? って思って自分で作りましたの記事です。

環境とか

プログラムの概要

  • ウィンドウ内の適当な位置をクリックすると正方形の箱が生成されて自由落下します
  • 描画にはBox2DのDebugDrawを使用します
  • 描画はSDL+OpenGLにのみ依存します
  • ベースはBox2D付属のHelloWorldです
  • ビルドする時は以下のライブラリにパスを通して下さい
    • SDL.lib
    • SDLmain.lib
    • Box2D.lib
    • opengl32.lib


ソースコード

#include <iostream>
#include <sstream>
#include <stdexcept>

#include <SDL.h>
#include <SDL_opengl.h>

#include <Box2D/Box2D.h>

// デバッグ描画クラス.
//中身はTestBedのFramework内のRender.cppから一部コピペ.
class CDebugDrawer : public b2Draw{
public:
	//コンストラクタ.何もしない.
	CDebugDrawer(){
	}

	//デストラクタ.継承なので.
	virtual ~CDebugDrawer(){
	}

	/// Draw a closed polygon provided in CCW order.
	virtual void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color){
		glColor3f(color.r, color.g, color.b);
		glBegin(GL_LINE_LOOP);
		for (int32 i = 0; i < vertexCount; ++i)
		{
			glVertex2f(vertices[i].x, vertices[i].y);
		}
		glEnd();
	}

	/// Draw a solid closed polygon provided in CCW order.
	virtual void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color){
		glEnable(GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glColor4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f);
		glBegin(GL_TRIANGLE_FAN);
		for (int32 i = 0; i < vertexCount; ++i)
		{
			glVertex2f(vertices[i].x, vertices[i].y);
		}
		glEnd();
		glDisable(GL_BLEND);

		glColor4f(color.r, color.g, color.b, 1.0f);
		glBegin(GL_LINE_LOOP);
		for (int32 i = 0; i < vertexCount; ++i)
		{
			glVertex2f(vertices[i].x, vertices[i].y);
		}
		glEnd();
	}


	/// Draw a circle.
	virtual void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color){
		const float32 k_segments = 16.0f;
		const float32 k_increment = 2.0f * b2_pi / k_segments;
		float32 theta = 0.0f;
		glColor3f(color.r, color.g, color.b);
		glBegin(GL_LINE_LOOP);
		for (int32 i = 0; i < k_segments; ++i)
		{
			b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
			glVertex2f(v.x, v.y);
			theta += k_increment;
		}
		glEnd();
	}
	
	/// Draw a solid circle.
	virtual void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color){
		const float32 k_segments = 16.0f;
		const float32 k_increment = 2.0f * b2_pi / k_segments;
		float32 theta = 0.0f;
		glEnable(GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glColor4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f);
		glBegin(GL_TRIANGLE_FAN);
		for (int32 i = 0; i < k_segments; ++i)
		{
			b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
			glVertex2f(v.x, v.y);
			theta += k_increment;
		}
		glEnd();
		glDisable(GL_BLEND);

		theta = 0.0f;
		glColor4f(color.r, color.g, color.b, 1.0f);
		glBegin(GL_LINE_LOOP);
		for (int32 i = 0; i < k_segments; ++i)
		{
			b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
			glVertex2f(v.x, v.y);
			theta += k_increment;
		}
		glEnd();

		b2Vec2 p = center + radius * axis;
		glBegin(GL_LINES);
		glVertex2f(center.x, center.y);
		glVertex2f(p.x, p.y);
		glEnd();
	}

	/// Draw a line segment.
	virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color){
		glColor3f(color.r, color.g, color.b);
		glBegin(GL_LINES);
		glVertex2f(p1.x, p1.y);
		glVertex2f(p2.x, p2.y);
		glEnd();
	}

	/// Draw a transform. Choose your own length scale.
	/// @param xf a transform.
	virtual void DrawTransform(const b2Transform& xf){
		b2Vec2 p1 = xf.p, p2;
		const float32 k_axisScale = 0.4f;
		glBegin(GL_LINES);
	
		glColor3f(1.0f, 0.0f, 0.0f);
		glVertex2f(p1.x, p1.y);
		p2 = p1 + k_axisScale * xf.q.GetXAxis();
		glVertex2f(p2.x, p2.y);

		glColor3f(0.0f, 1.0f, 0.0f);
		glVertex2f(p1.x, p1.y);
		p2 = p1 + k_axisScale * xf.q.GetYAxis();
		glVertex2f(p2.x, p2.y);

		glEnd();
	}
};

using namespace std;

namespace{
	const int _ScreenWidth = 640;	//スクリーン解像度
	const int _ScreenHeight = 480;
	const float _DrawScaler = 0.5f;	//描画時の座標倍率
}

//SDLとOpenGLの初期化を行う
void InitSDLGL(){
	//SDLの初期化
	if( SDL_Init(SDL_INIT_VIDEO) ){
		ostringstream oss;
		oss << "SDLの初期化に失敗" << endl;
		oss << SDL_GetError() << endl;
		throw runtime_error(oss.str());
	}

	//バッファリングとVSYNCの設定
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

	//ウィンドウを生成
	SDL_Surface* pScreen = SDL_SetVideoMode(_ScreenWidth, _ScreenHeight, 0, SDL_OPENGL);
	if(!pScreen){
		ostringstream oss;
		oss << "SDLによるOpenGLの初期化に失敗" << endl;
		oss << SDL_GetError() << endl;
		throw runtime_error(oss.str());
	}

	//OpenGLの描画設定
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glViewport(0.0f, 0.0f, _ScreenWidth, _ScreenHeight);
	glOrtho(-_ScreenWidth/2.f*_DrawScaler, _ScreenWidth/2.f*_DrawScaler, -_ScreenHeight/2.f*_DrawScaler, _ScreenHeight/2.f*_DrawScaler, 1.f, -1.f);
}

//SDLとOpenGLの解放を行う
void ReleaseSDLGL(){
	SDL_Quit();
}

//指定座標に箱を追加
void AddBox(b2World& world, int mouse_x, int mouse_y){
	float x = (mouse_x-(_ScreenWidth/2))*_DrawScaler;
	float y = (-mouse_y+(_ScreenHeight/2))*_DrawScaler;
	//動く箱を生成
	b2BodyDef BodyDef;
	BodyDef.type = b2_dynamicBody;
	BodyDef.position.Set(x, y);
	b2Body* pBody = world.CreateBody(&BodyDef);
	b2PolygonShape DynamicBox;
	DynamicBox.SetAsBox(5.f, 5.f);
	b2FixtureDef FixtureDef;
	FixtureDef.shape = &DynamicBox;
	FixtureDef.density = 1.f;
	FixtureDef.friction = 0.3f;
	pBody->CreateFixture(&FixtureDef);
}

//このプログラムのメインループ
void MainLoop(){
	//ワールドを生成
	b2Vec2 Gravity(0.0f, -10.0f);
	b2World World(Gravity);

	//ワールドのデバッグ描画を設定
	CDebugDrawer DebugDrawer;
	DebugDrawer.SetFlags(0x1F);
	World.SetDebugDraw(&DebugDrawer);

	//地形ボディを生成
	b2BodyDef GroundBodyDef;
	GroundBodyDef.position.Set(0.f, -10.f);
	b2Body* pGroundBody = World.CreateBody(&GroundBodyDef);
	b2PolygonShape GroundBox;
	GroundBox.SetAsBox(50.f, 10.f);
	pGroundBody->CreateFixture(&GroundBox, 0.f);

	const float32 TimeStep = 1.f/60.f;
	const int32 VelocityIterations = 6;
	const int32 PositionIterations = 2;

	//メッセージループ
	for(;;){
		//SDLメッセージを処理
		SDL_Event Event;
		while(SDL_PollEvent(&Event)){
			switch(Event.type){
			case SDL_QUIT:
				return;
			case SDL_MOUSEBUTTONDOWN:
				AddBox(World, Event.motion.x, Event.motion.y);
				break;
			default:
				break;
			}
		}
		//Box2Dワールドを次のステップに進ませる
		World.Step(TimeStep, VelocityIterations, PositionIterations);
		//結果をデバッグ描画
		glClear(GL_COLOR_BUFFER_BIT);
		World.DrawDebugData();
		SDL_GL_SwapBuffers();
	}
}

//エントリ関数
int main(int argc, char *argv[]){
	//SDL with OpenGLの初期化
	InitSDLGL();

	//メインループ
	MainLoop();

	//SDL with OpenGLの解放
	ReleaseSDLGL();

	return 0;
}

感想とか

コレをベースにマニュアル読み進めればイケそうな気がする。