Initializing Box2D into SDL?

Hey everyone!! I downloaded a library called box2d and had it linked into my engine/project and the testbed worked out ok(meaning I didn’t encounter any issues) and had a console giving me numbers and a text saying success(for those who worked with box2d with sdl). However, I noticed that in some tests in box2d, glfw, glad, and imgui are being used to render shapes with box2d, but im using sdl to render a dang box to fall onto the bottom of the screen! So, for anyone who uses box2d with sdl with NO OPENGL, how did you get sdl and box2d working together with visual results?

Box2D as a physics engine doesn’t imply any drawing capabilities, afaik there’s no magic glue for such configuration (it’s highly depending of the project itself and should be implemented as a scene engine). Box2D’s FAQ on drawing clearly states it: Box2D: FAQ.
So,
implement your drawing yourself using testbeds as the reference (plenty of examples on the web tho) OR don’t reinvent the wheel and use a framework like GitHub - LucidSigma/stardust: A 2D game engine/framework built with C++20, SDL2, and OpenGL. (this one is built around Box2D and SDL2 so it’s possibly a best match for you)

Sorry for late reply, but just look into the testbed and see how they work? Opengl will not be used, but sdl will

Box2D is only a physics engine. How you draw stuff is up to you.

Visualization is very important for debugging collision and physics. I wrote the test bed to help me test Box2D and give you examples of how to use Box2D. The TestBed is not part of the Box2D library.

TestBed is just a debug helper and most importantly a practical example how one can draw graphics. You as a programmer must implement everything yourself if you decide so (and you decided to). You have to study the Box2D first, or maybe “translate” GL to SDL2 calls (GL understanding required), to get things work.

One way to draw the world is to iterate the bodies and draw them on screen:


void MyApp_DrawBody(const b2Body *b) {
  auto &t = b->GetTransform();
  for (auto f = b->GetFixtureList(); f; f = f->GetNext()) {
    // Draw using SDL2 functions
  }
}

...

for (auto b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    MyApp_DrawBody(b):
}

Please show the code that you have so far, for rendering a box that’s supposed to represent a box in the Box2D physics world, and someone here might be able to spot what the problem is.

For this code, I’m using visual studio community 2019:

#include <stdio.h>
#include <SDL.h>
#include <box2d.h>
#include <test.h>

int main(int argc, char** argv)
{

if (SDL_Init(SDL_INIT_VIDEO)) {
	printf("Error: %s", SDL_GetError());
	return -1;
}
SDL_Window* window = SDL_CreateWindow("Box2D Attempt First Try", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, 0);
if (!window) {
	printf("Error: %s", SDL_GetError());
	return -1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
	printf("Error: %s", SDL_GetError());
	return -1;
}




bool quit = false;
uint64_t lastFrameTime = SDL_GetPerformanceCounter();
while (!quit) 
{
	SDL_Event event;
	while (SDL_PollEvent(&event) != 0) {
		if (event.type == SDL_QUIT) {
			quit = true;
			break;
		}
	}

	//SDL_SetRenderDrawColor(renderer, 4, 3, 96, 255);
	SDL_RenderClear(renderer);



	SDL_RenderClear(renderer);

	SDL_Rect rect;
	rect.x = 250;
	rect.y = 150;
	rect.w = 200;
	rect.h = 200;

	SDL_SetRenderDrawColor(renderer, 39, 64, 255, 255);
	SDL_RenderDrawRect(renderer, &rect);

	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

	SDL_RenderPresent(renderer);


	SDL_RenderClear(renderer);


	b2Vec2 gravity(0.0f, -10.0f);


	b2World world(gravity);

	b2BodyDef groundBodyDef;
	groundBodyDef.position.Set(0.0f, -10.0f);


	b2Body* groundBody = world.CreateBody(&groundBodyDef);


	b2PolygonShape groundBox;

	groundBox.SetAsBox(50.0f, 10.0f);

	groundBody->CreateFixture(&groundBox, 0.0f);


	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	bodyDef.position.Set(0.0f, 4.0f);
	b2Body* body = world.CreateBody(&bodyDef);


	b2PolygonShape dynamicBox;
	dynamicBox.SetAsBox(1.0f, 1.0f);


	b2FixtureDef fixtureDef;
	fixtureDef.shape = &dynamicBox;


	fixtureDef.density = 1.0f;

	fixtureDef.friction = 0.3f;


	body->CreateFixture(&fixtureDef);


	float timeStep = 1.0f / 60.0f;
	int32 velocityIterations = 6;
	int32 positionIterations = 2;

	b2Vec2 position = body->GetPosition();
	float angle = body->GetAngle();

	// This is our little game loop.
	for (int32 i = 0; i < 60; ++i)
	{

		world.Step(timeStep, velocityIterations, positionIterations);


		position = body->GetPosition();
		angle = body->GetAngle();

		printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle);
	}


	//Box2D attempt to attacth rect to box2d body but nothing but a blue box
	rect.x = position.x;
	rect.y = position.y;

}

SDL_DestroyWindow(window);
SDL_Quit();
return 0;

}

This is what I got so far. Right now, im trying to simulate the blue box to just fall down to the screen(by this happening, i’ll know box2d is indeed working, then i move to the next step).

What many people (me included, when I was new to Box2D) do wrong is assuming that Box2D works in pixels. This is incorrect. Box2D works in meters for its positions and sizes.
When creating physics objects and deciding their position and size in the physics world, Box2D expect the values that it receives to be in meters.

Let’s say you were to create a physics body and position it like this:
bodyDef.position.Set(100.0f, 100.0f);
This will not position the physics body at a pixel position of 100x100 on the screen.
What will happen instead is that the position of the physics body will be 100x100 meters from the origin of the physics world.

The same goes with sizes. In your code above, you’re doing this:
groundBox.SetAsBox(50.0f, 10.0f);
This will not create a physics box with a size of 50x10 pixels.
What will happen instead is that a box of 50x10 meters will be created.

This means that if you were to use an SDL_Rect that’s rendered at the physics body’s position, and with the same size as the physics body, it might not show up at the position that you expect on the screen, and also not in the size that you expected.

What many people do (me included) to solve this is by using a meters-per-pixel-factor that’s used to convert from pixels to meters, and vice versa. What this meters-per-pixel-factor represents is a value that tells what 1 meters represents in pixels. The value of this depends on the game etc and can be whatever you want. A meters-per-pixel-factor of 32 means that 1 meter is 32 pixels, for example.
So when creating a physics body/box, its position is first converted from a pixel position on the screen to a position in meters, and same goes for its size.

Below I’ve attached a code example on how the actual conversion can be done.
In the case of my code example, I’ve used some helper functions that converts from pixels to meters (and vice versa), but some macros or something else can of course also be used, if preferred.

Note that positions and sizes are converted from pixels to meters when passed to Box2D, and also note that the position that is received from Box2D (see the Update function) is converted from meters to pixels before positioning the SDL_Rect properly on the screen.

Code example
#include <SDL.h>
#include <box2d.h>

#define WINDOW_WIDTH	(800)
#define WINDOW_HEIGHT	(600)

SDL_Window* pWindow = nullptr;
SDL_Renderer* pRenderer = nullptr;

b2World* pB2World = nullptr;
b2Body* pFallingBoxBody = nullptr;

SDL_FRect RectGround = {0.0f, 0.0f, 0.0f, 0.0f};
SDL_FRect RectFallingBox = {0.0f, 0.0f, 0.0f, 0.0f};

double OldTime = 0.0;
double NewTime = 0.0;
double DeltaTime = 0.0;

float MetersPerPixelFactor = 32.0f;

bool Running = true;

// Some helper functions to convert from pixels to meters, and vice versa

float PixelToMeter(const float Value)
{
	return (Value * (1.0f / MetersPerPixelFactor));
}

b2Vec2 PixelToMeter(const b2Vec2& rVector)
{
	return b2Vec2(PixelToMeter(rVector.x), PixelToMeter(rVector.y));
}

float MeterToPixel(const float Value)
{
	return (Value * MetersPerPixelFactor);
}

b2Vec2 MeterToPixel(const b2Vec2& rVector)
{
	return b2Vec2(MeterToPixel(rVector.x), MeterToPixel(rVector.y));
}

void InitializeAssets()
{
	// Create a ground rectangle

	b2Vec2 Size		= b2Vec2(WINDOW_WIDTH, 40.0f);
	b2Vec2 SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	b2Vec2 Position	= b2Vec2(WINDOW_WIDTH * 0.5f, WINDOW_HEIGHT - (Size.y * 0.5f));

	b2BodyDef BodyDefinition;
	BodyDefinition.type = b2BodyType::b2_staticBody;

	// Box2D works in meters, so convert 'Position' from a position in pixels to a position in meters
	BodyDefinition.position	= PixelToMeter(Position);

	b2PolygonShape Shape;

	// Box2D works in meters so convert 'SizeHalf' from a size in pixels to a size in meters
	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	b2FixtureDef FixtureDefinition;
	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.1f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pGroundBody = pB2World->CreateBody(&BodyDefinition);
	pGroundBody->CreateFixture(&FixtureDefinition);

	// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
	RectGround = {Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y};

	//////////////////////////////////////////////////////////////////////////

	// Create a falling box

	Size		= b2Vec2(32.0f, 32.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2(WINDOW_WIDTH * 0.5f, -32.0f);

	BodyDefinition.type = b2BodyType::b2_dynamicBody;
	BodyDefinition.position	= PixelToMeter(Position);

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.5f;
	FixtureDefinition.density		= 0.5f;

	// Will be used in Update to move the RectFallingBox along with the falling box body
	pFallingBoxBody = pB2World->CreateBody(&BodyDefinition);
	pFallingBoxBody->CreateFixture(&FixtureDefinition);

	RectFallingBox = {Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y};
}

void Update()
{
	NewTime = (double)SDL_GetTicks();
	DeltaTime = (NewTime - OldTime) * 0.001;
	OldTime = NewTime;

	pB2World->Step((float)DeltaTime, 8, 4);

	// Box2D works in meters so convert from the body's meter position to a position in pixels
	const b2Vec2 BoxPositionPixel = MeterToPixel(pFallingBoxBody->GetPosition());

	// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
	RectFallingBox.x = BoxPositionPixel.x - (RectFallingBox.w * 0.5f);
	RectFallingBox.y = BoxPositionPixel.y - (RectFallingBox.h * 0.5f);
}

void Render()
{
	SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
	SDL_RenderClear(pRenderer);

	// Render the ground box
	SDL_SetRenderDrawColor(pRenderer, 114, 89, 89, 255);
	SDL_RenderFillRectF(pRenderer, &RectGround);

	// Render the falling box
	SDL_SetRenderDrawColor(pRenderer, 63, 114, 63, 255);
	SDL_RenderFillRectF(pRenderer, &RectFallingBox);

	SDL_RenderPresent(pRenderer);
}

int main(int argc, char** argv)
{
	// Initialize SDL, create the window and the renderer, initialize Box2D etc
	if(!Initialize())
		return -1;

	InitializeAssets();

	while(Running)
	{
		// Assume SDL_PollEvent etc is called in this function
		HandleEvents();

		Update();
		Render();
	}

	// Shutdown/destroy everything
	Deinitialize();

	return 0;
}

What you would see if you were to execute this code example is a box falling from the top down to a ground box:

PhysicsExample

Thank you for the response. I did try to run this code in vc 2019 and got this


Did I do something wrong?

Based on the error messages, you should be able to spot the problem.

I tried to keep the code short so I didn’t provide any code for those three functions missing, since you already know how to do that. They are meant as functions that create/destroy the window, the renderer, Box2D etc so just add code for that.

Thank you for clarifying that. However, I wish to share you my code based on what you gave me and see if you can correct my work based on the functions I created with your hard work:

#include <SDL.h>
#include <box2d.h>

#define WINDOW_WIDTH (800)
#define WINDOW_HEIGHT (600)

SDL_Window* pWindow;
SDL_Renderer* pRenderer;

b2World* pB2World;
b2Body* pFallingBoxBody;

SDL_FRect RectGround = { 0.0f, 0.0f, 0.0f, 0.0f };
SDL_FRect RectFallingBox = { 0.0f, 0.0f, 0.0f, 0.0f };

double OldTime = 0.0;
double NewTime = 0.0;
double DeltaTime = 0.0;

float MetersPerPixelFactor = 32.0f;

bool Running = true;

// Some helper functions to convert from pixels to meters, and vice versa

float PixelToMeter(const float Value)
{
return (Value * (1.0f / MetersPerPixelFactor));
}

b2Vec2 PixelToMeter(const b2Vec2& rVector)
{
return b2Vec2(PixelToMeter(rVector.x), PixelToMeter(rVector.y));
}

float MeterToPixel(const float Value)
{
return (Value * MetersPerPixelFactor);
}

b2Vec2 MeterToPixel(const b2Vec2& rVector)
{
return b2Vec2(MeterToPixel(rVector.x), MeterToPixel(rVector.y));
}

void InitializeAssets()
{
// Create a ground rectangle

b2Vec2 Size = b2Vec2(WINDOW_WIDTH, 40.0f);
b2Vec2 SizeHalf = b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
b2Vec2 Position = b2Vec2(WINDOW_WIDTH * 0.5f, WINDOW_HEIGHT - (Size.y * 0.5f));

b2BodyDef BodyDefinition;
BodyDefinition.type = b2BodyType::b2_staticBody;

// Box2D works in meters, so convert 'Position' from a position in pixels to a position in meters
BodyDefinition.position = PixelToMeter(Position);

b2PolygonShape Shape;

// Box2D works in meters so convert 'SizeHalf' from a size in pixels to a size in meters
Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

b2FixtureDef FixtureDefinition;
FixtureDefinition.shape = &Shape;
FixtureDefinition.friction = 0.2f;
FixtureDefinition.restitution = 0.1f;
FixtureDefinition.density = 1.0f;

b2Body* pGroundBody = pB2World->CreateBody(&BodyDefinition);
pGroundBody->CreateFixture(&FixtureDefinition);

// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
RectGround = { Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y };

//////////////////////////////////////////////////////////////////////////

// Create a falling box

Size = b2Vec2(32.0f, 32.0f);
SizeHalf = b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
Position = b2Vec2(WINDOW_WIDTH * 0.5f, -32.0f);

BodyDefinition.type = b2BodyType::b2_dynamicBody;
BodyDefinition.position = PixelToMeter(Position);

Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

FixtureDefinition.shape = &Shape;
FixtureDefinition.friction = 0.2f;
FixtureDefinition.restitution = 0.5f;
FixtureDefinition.density = 0.5f;

// Will be used in Update to move the RectFallingBox along with the falling box body
pFallingBoxBody = pB2World->CreateBody(&BodyDefinition);
pFallingBoxBody->CreateFixture(&FixtureDefinition);

RectFallingBox = { Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y };

}

void Update()
{
NewTime = (double)SDL_GetTicks();
DeltaTime = (NewTime - OldTime) * 0.001;
OldTime = NewTime;

pB2World->Step((float)DeltaTime, 8, 4);

// Box2D works in meters so convert from the body's meter position to a position in pixels
const b2Vec2 BoxPositionPixel = MeterToPixel(pFallingBoxBody->GetPosition());

// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
RectFallingBox.x = BoxPositionPixel.x - (RectFallingBox.w * 0.5f);
RectFallingBox.y = BoxPositionPixel.y - (RectFallingBox.h * 0.5f);

}

void Render()
{
SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
SDL_RenderClear(pRenderer);

// Render the ground box
SDL_SetRenderDrawColor(pRenderer, 114, 89, 89, 255);
SDL_RenderFillRectF(pRenderer, &RectGround);

// Render the falling box
SDL_SetRenderDrawColor(pRenderer, 63, 114, 63, 255);
SDL_RenderFillRectF(pRenderer, &RectFallingBox);

SDL_RenderPresent(pRenderer);

}

void HandleEvents()
{
InitializeAssets();

}

void Deinitialize()
{
//Destroy window
SDL_DestroyWindow(pWindow);

//Quit SDL subsystems
SDL_Quit();

}

int main(int argc, char** argv)
{

bool Initialize = true;
if (Initialize == true)
{
	pWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN);
	pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	Running = true;
}



// Initialize SDL, create the window and the renderer, initialize Box2D etc
if (Initialize == false)
{
	return -1;
}
	

InitializeAssets();

while (Running)
{
	// Assume SDL_PollEvent etc is called in this function
	HandleEvents();

	Update();
	Render();
}

//So the window can stay up and not close
SDL_Event e; bool quit = false; while (quit == false) { while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) quit = true; } }


// Shutdown/destroy everything
Deinitialize();

return 0;

}

Hope you don’t mind, but I did change a couple of things while trying to make everything exactly the same you made

There’s a few issues with your code:

  1. You’re not initializing/starting up SDL before creating the SDL window and renderer. because of this, you’ll get a crash when trying to use SDL-specific functions, like SDL_CreateWindow for example.

  2. In the Deinitialize function, you’re not destroying the SDL renderer.
    Every object that is created with any of the SDL_Create* functions should always be destroyed with corresponding SDL_Destroy* function.
    In this case, you should in the Deinitialize function add a call to SDL_DestroyRenderer(pRenderer);.

  3. You’re not initializing/starting up Box2D by creating a physics world. Because of this, you’ll get a crash when trying to use Box2D-specific, like pB2World->CreateBody for example.

  4. You’re calling the InitializeAssets function every frame. Assets should only be created once, during the startup of the game. You’re calling the InitializeAssets function inside the HandleEvents, which is executed every frame.

  5. The SDL_PollEvent is meant to be called every frame, inside the application’s/game’s mainloop (while(Running)) and not after it.
    It looks like you’re starting up a new mainloop (while (quit == false) {}) when the first mainloop is finished.

I’ve attached a new code example below, where all the above is fixed and moved to it’s proper place.
This time the code includes a Initialize-, Deinitialize- and HandleEvents function, so you should be able to just copy-paste all the code, run it and see a box falling down onto a ground rectangle, just like in the gif I attached to my previous reply.

Summary
#include <iostream>
#include <SDL.h>
#include <box2d.h>

#define WINDOW_WIDTH	(800)
#define WINDOW_HEIGHT	(600)

// The physics simulation will be simulated at a fixed timestep of 0.016, i.e 60FPS
#define FIXED_TIMESTEP	(1.0 / 60.0)

SDL_Window* pWindow = nullptr;
SDL_Renderer* pRenderer = nullptr;

b2World* pB2World = nullptr;
b2Body* pFallingBoxBody = nullptr;

SDL_FRect RectGround = {0.0f, 0.0f, 0.0f, 0.0f};
SDL_FRect RectFallingBox = {0.0f, 0.0f, 0.0f, 0.0f};

double OldTime = 0.0;
double NewTime = 0.0;
double DeltaTime = 0.0;

float MetersPerPixelFactor = 32.0f;

bool Running = true;

// Some helper functions to convert from pixels to meters, and vice versa

float PixelToMeter(const float Value)
{
	return (Value * (1.0f / MetersPerPixelFactor));
}

b2Vec2 PixelToMeter(const b2Vec2& rVector)
{
	return b2Vec2(PixelToMeter(rVector.x), PixelToMeter(rVector.y));
}

float MeterToPixel(const float Value)
{
	return (Value * MetersPerPixelFactor);
}

b2Vec2 MeterToPixel(const b2Vec2& rVector)
{
	return b2Vec2(MeterToPixel(rVector.x), MeterToPixel(rVector.y));
}

bool Initialize()
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		printf("Error: SDL could not initialize! %s\n", SDL_GetError());
		return false;
	}

	pWindow = SDL_CreateWindow("SDL + Box2D", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
	if(!pWindow)
	{
		printf("Error: SDL window could not be created! %s\n", SDL_GetError());
		return false;
	}

	pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if(!pRenderer)
	{
		printf("Error: SDL renderer could not be created! %s\n", SDL_GetError());
		return false;
	}

	SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, 255);
	SDL_SetRenderDrawBlendMode(pRenderer, SDL_BLENDMODE_BLEND);

	SDL_RenderClear(pRenderer);
	SDL_RenderPresent(pRenderer);

	pB2World = new b2World(b2Vec2(0.0f, 9.82f));

	return true;
}

void Deinitialize()
{
	delete pB2World;
	pB2World = nullptr;

	SDL_DestroyRenderer(pRenderer);
	pRenderer = nullptr;

	SDL_DestroyWindow(pWindow);
	pWindow = nullptr;

	SDL_Quit();
}

void InitializeAssets()
{
	// Create a ground rectangle

	b2Vec2 Size		= b2Vec2(WINDOW_WIDTH, 40.0f);
	b2Vec2 SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	b2Vec2 Position	= b2Vec2(WINDOW_WIDTH * 0.5f, WINDOW_HEIGHT - (Size.y * 0.5f));

	b2BodyDef BodyDefinition;
	BodyDefinition.type = b2BodyType::b2_staticBody;

	// Box2D works in meters, so convert 'Position' from a position in pixels to a position in meters
	BodyDefinition.position	= PixelToMeter(Position);

	b2PolygonShape Shape;

	// Box2D works in meters so convert 'SizeHalf' from a size in pixels to a size in meters
	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	b2FixtureDef FixtureDefinition;
	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.1f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pGroundBody = pB2World->CreateBody(&BodyDefinition);
	pGroundBody->CreateFixture(&FixtureDefinition);

	// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
	RectGround = {Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y};

	//////////////////////////////////////////////////////////////////////////

	// Create a falling box

	Size		= b2Vec2(32.0f, 32.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2(WINDOW_WIDTH * 0.5f, -32.0f);

	BodyDefinition.type = b2BodyType::b2_dynamicBody;
	BodyDefinition.position	= PixelToMeter(Position);

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.5f;
	FixtureDefinition.density		= 0.5f;

	// Will be used in Update to move the RectFallingBox along with the falling box body
	pFallingBoxBody = pB2World->CreateBody(&BodyDefinition);
	pFallingBoxBody->CreateFixture(&FixtureDefinition);

	RectFallingBox = {Position.x - SizeHalf.x, Position.y - SizeHalf.y, Size.x, Size.y};
}

void HandleEvents()
{
	SDL_Event Event;
	while(SDL_PollEvent(&Event))
	{
		switch(Event.type)
		{
			case SDL_QUIT:
			{
				Running = false;
				break;
			}

			default:
				break;
		}
	}
}

void Update()
{
	NewTime = (double)SDL_GetTicks();
	DeltaTime = (NewTime - OldTime) * 0.001;
	OldTime = NewTime;

	pB2World->Step((float)DeltaTime, 8, 4);

	// Box2D works in meters so convert from the body's meter position to a position in pixels
	const b2Vec2 BoxPositionPixel = MeterToPixel(pFallingBoxBody->GetPosition());

	// The origin in Box2D is in the middle of the object and in SDL the origin is in the upper left corner, so move the rect up and to the left by the half of its size
	RectFallingBox.x = BoxPositionPixel.x - (RectFallingBox.w * 0.5f);
	RectFallingBox.y = BoxPositionPixel.y - (RectFallingBox.h * 0.5f);
}

void Render()
{
	SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
	SDL_RenderClear(pRenderer);

	// Render the ground box
	SDL_SetRenderDrawColor(pRenderer, 114, 89, 89, 255);
	SDL_RenderFillRectF(pRenderer, &RectGround);

	// Render the falling box
	SDL_SetRenderDrawColor(pRenderer, 63, 114, 63, 255);
	SDL_RenderFillRectF(pRenderer, &RectFallingBox);

	SDL_RenderPresent(pRenderer);
}

int main(int argc, char** argv)
{
	// Initialize SDL, create the window and the renderer, initialize Box2D etc
	if(!Initialize())
		return -1;

	InitializeAssets();

	while(Running)
	{
		HandleEvents();
		Update();
		Render();
	}

	// Shutdown/destroy everything
	Deinitialize();

	return 0;
}

Thank you very much!! I’ll have to admit that my knowledge for box2d and sdl is very low(well, box2d anyway) but you really saved my ass! In the code it does indeed bounce, but i would like to just have the box fall without bouncing. Can you tell me in the code were i can just prevent the bouncing?

You’re welcome.

In the InitializeAssets function, I’m creating 2 physics objects; one for the ground box and one for the falling box. Both of those physics objects are created based on the parameters set in the FixtureDefinition.
In the FixtureDefinition, for either both of the physics objects or just the falling box, set the restitution (bounciness) parameter to 0.0f. This will make the physics object(s) not bounce at all. The higher the restitution value, the more the physics object(s) will bounce.

Box2D’s documentation contains quite a lot of information but I highly recommended you to at least skim through it to familiarize yourself with Box2D’s concepts and functionality.

https://box2d.org/documentation/

Wow! Damn man, you really are the best. I appreciate everything you did!!

No worries, glad to help. :slight_smile:

If you’re planning on making a game of sort, don’t forget to split up the source code into separate files, classes etc. The code I provided is a simple example, where it’s okay to have it all in one source file, but in a bigger project, all the code should never be in one large file.

Good luck with your project!

1 Like

Hey daniel! Firstly, I wish to thank you for all your work into box2d in sdl. However, I wish to go a step further based on what we established. You see, I want to do 3 three things:

  1. The brown rectangle is being stretched into the screen based on the width and coming up with a way to just render the brown rectangle based on position, not width or height.

  2. the green box does indeed fall and land onto the brown rectangle, but for curiosity, I wish to have multiple boxes fall onto the ground and have them stacked on one another and a couple of rectangles tipping over and just fall flat on the ground.

  3. drawing multiple grounds for the boxes to collide and fall onto the ground.

I was wondering if I could get your theory on how I can go with these 3 things?

Hey.

I’m unsure what you mean by this:

  1. The brown rectangle is being stretched into the screen based on the width and coming up with a way to just render the brown rectangle based on position, not width or height.

What i mean by that, is that when i change the width of the screen, the brown rectangle stretches the entire way on the screen. My idea is to just have the ground have a start point and an end point(from point A to point B).

It’s rather simple to create more Box2D bodies inside the InitializeAssets function. You have some code there that can be reused to create more physics objects.

The problem comes to rendering objects that’s supposed to rotate, using SDL.
SDL_Rect/SDL_FRect objects can’t be rendered rotated.
One alternative is to have an SDL_Texture in the shape of the physics object that you want to render and then render the texture at the physics object’s position.

One other alternative is to use a functionality inside Box2D called debug-draw. What this mean is that you provide a class to Box2D that contains some debug-draw functions and Box2D will provide the vertices (points) of the physics body’s shape(s), and you render that/those shape(s) based on the provided information.
Such debug-draw class can be rather tricky to get right and get the physics bodies to render properly, so below I’ve provided such class for you.

To get all of this to work you need to follow this properly:

  1. Create a file called Box2DDebugRenderer.h and copy-paste the following code into that file:
Click here to view Box2DDebugRenderer.h code
#pragma once

#include <box2d.h>
#include <SDL.h>

class CBox2DDebugRenderer : public b2Draw
{
public:

	 CBox2DDebugRenderer();
	 CBox2DDebugRenderer(SDL_Renderer* pRenderer, const float MetersPerPixelFactor, const uint32_t DrawFlags);
	~CBox2DDebugRenderer();

	virtual void	DrawPolygon			(const b2Vec2* pVertices, int32 VertexCount, const b2Color& rColor) override				{}
	virtual void	DrawSolidPolygon	(const b2Vec2* pVertices, int32 VertexCount, const b2Color& rColor) override;
	virtual void	DrawCircle			(const b2Vec2& rCenter, float Radius, const b2Color& rColor) override						{}
	virtual void	DrawSolidCircle		(const b2Vec2& rCenter, float Radius, const b2Vec2& rAxis, const b2Color& rColor) override	{}
	virtual void	DrawSegment			(const b2Vec2& rPoint1, const b2Vec2& rPoint2, const b2Color& rColor) override				{}
	virtual void	DrawTransform		(const b2Transform& rTransform) override													{}
	virtual void	DrawPoint			(const b2Vec2& rPoint, float Size, const b2Color& rColor) override							{}

private:

	float			MeterToPixel		(const float Value);
	b2Vec2			MeterToPixel		(const b2Vec2& rVector);
	SDL_Color		ToSDLColor			(const b2Color& rColor);

private:

	SDL_Renderer*	m_pRenderer;

	float			m_MetersPerPixelFactor;

};
  1. Create a file called Box2DDebugRenderer.cpp and copy-paste the following code into that file:
Click here to view Box2DDebugRenderer.cpp code
#include "Box2DDebugRenderer.h"

#include <vector>

CBox2DDebugRenderer::CBox2DDebugRenderer()
: m_pRenderer(nullptr)
, m_MetersPerPixelFactor(0.0f)
{

}

CBox2DDebugRenderer::CBox2DDebugRenderer(SDL_Renderer* pRenderer, const float MetersPerPixelFactor, const uint32_t DrawFlags)
: m_pRenderer(pRenderer)
, m_MetersPerPixelFactor(MetersPerPixelFactor)
{
	SetFlags(DrawFlags);
}

CBox2DDebugRenderer::~CBox2DDebugRenderer()
{

}

void CBox2DDebugRenderer::DrawSolidPolygon(const b2Vec2* pVertices, int32 VertexCount, const b2Color& rColor)
{
	const SDL_Color			LineColor		= ToSDLColor(rColor);
	const SDL_Color			TriangleColor	= ToSDLColor(b2Color(rColor.r * 0.5f, rColor.g * 0.5f, rColor.b * 0.5f, rColor.a * 0.5f));
	std::vector<SDL_Vertex> Vertices;

	for(int32_t i = 0; i < VertexCount; ++i)
	{
		if(i < (VertexCount - 2))
		{
			const b2Vec2 TriangleVertex[3] =
			{
				MeterToPixel(pVertices[0 + 0]),
				MeterToPixel(pVertices[i + 1]),
				MeterToPixel(pVertices[i + 2]),
			};

			for (int32_t j = 0; j < 3; ++j)
			{
				Vertices.push_back({{TriangleVertex[j].x, TriangleVertex[j].y}, TriangleColor, {0.0f, 0.0f}});
			}
		}
	}

	SDL_RenderGeometry(m_pRenderer, nullptr, Vertices.data(), (int32_t)Vertices.size(), nullptr, 0);

	SDL_SetRenderDrawColor(m_pRenderer, LineColor.r, LineColor.g, LineColor.b, LineColor.a);

	for(int32_t i = 0; i < VertexCount; ++i)
	{
		const b2Vec2 LineStart	= MeterToPixel(pVertices[i]);
		const b2Vec2 LineEnd	= MeterToPixel(pVertices[((i < (VertexCount - 1)) ? (i + 1) : 0)]);

		SDL_RenderDrawLineF(m_pRenderer, LineStart.x, LineStart.y, LineEnd.x, LineEnd.y);
	}
}

float CBox2DDebugRenderer::MeterToPixel(const float Value)
{
	return (Value * m_MetersPerPixelFactor);
}

b2Vec2 CBox2DDebugRenderer::MeterToPixel(const b2Vec2& rVector)
{
	return b2Vec2(MeterToPixel(rVector.x), MeterToPixel(rVector.y));
}

SDL_Color CBox2DDebugRenderer::ToSDLColor(const b2Color& rColor)
{
	return {(uint8_t)(rColor.r * 255.0f), (uint8_t)(rColor.g * 255.0f), (uint8_t)(rColor.b * 255.0f), (uint8_t)(rColor.a * 255.0f)};
}

It’s not very important to know exactly what happens in the debug-renderer class. Just know that it takes in the information about the physics body’s shape(s) and renders a representation of the shape(s) using triangles and lines.

The main application will include this class at the top of the application file and set Box2D to use this class for doing the debug-rendering.

If you check out the InitializeAssets function, you’ll see that I’m creating a ground body that’s not fixed to the window size and can be whichever size it’s set to, the same with the left/right wall, as well as the two slope grounds I’m creating.
Besides creating the ground, walls and slopes, I’m also creating some boxes that falls down from the top of the window onto the ground and slopes.

Here’s the code for the main application:

Click to view main application code
#include <iostream>

#include <SDL.h>
#include <box2d.h>

#include "Box2DDebugRenderer.h"

#define WINDOW_WIDTH	(1280)
#define WINDOW_HEIGHT	(720)
#define DEG_TO_RAD(X)	((X) * ((float)M_PI / 180.0f))

SDL_Window*		pWindow		= nullptr;
SDL_Renderer*	pRenderer	= nullptr;

b2World*				pB2World			= nullptr;
CBox2DDebugRenderer*	pB2DebugRenderer	= nullptr;

double OldTime	 = 0.0;
double NewTime	 = 0.0;
double DeltaTime = 0.0;

float MetersPerPixelFactor = 32.0f;

bool Running = true;

// Some helper functions to convert from pixels to meters, and vice versa

float PixelToMeter(const float Value)
{
	return (Value * (1.0f / MetersPerPixelFactor));
}

b2Vec2 PixelToMeter(const b2Vec2& rVector)
{
	return b2Vec2(PixelToMeter(rVector.x), PixelToMeter(rVector.y));
}

float MeterToPixel(const float Value)
{
	return (Value * MetersPerPixelFactor);
}

b2Vec2 MeterToPixel(const b2Vec2& rVector)
{
	return b2Vec2(MeterToPixel(rVector.x), MeterToPixel(rVector.y));
}

bool Initialize()
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		printf("Error: SDL could not initialize! %s\n", SDL_GetError());
		return false;
	}

	pWindow = SDL_CreateWindow("SDL + Box2D", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
	if(!pWindow)
	{
		printf("Error: SDL window could not be created! %s\n", SDL_GetError());
		return false;
	}

	pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if(!pRenderer)
	{
		printf("Error: SDL renderer could not be created! %s\n", SDL_GetError());
		return false;
	}

	SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, 255);
	SDL_SetRenderDrawBlendMode(pRenderer, SDL_BLENDMODE_BLEND);

	SDL_RenderClear(pRenderer);
	SDL_RenderPresent(pRenderer);

	pB2World			= new b2World(b2Vec2(0.0f, 9.82f));
	pB2DebugRenderer	= new CBox2DDebugRenderer(pRenderer, MetersPerPixelFactor, b2Draw::e_shapeBit);

	pB2World->SetDebugDraw(pB2DebugRenderer);

	return true;
}

void Deinitialize()
{
	delete pB2DebugRenderer;
	pB2DebugRenderer = nullptr;

	pB2World->SetDebugDraw(nullptr);
	pB2World->ClearForces();
	delete pB2World;
	pB2World = nullptr;

	SDL_DestroyRenderer(pRenderer);
	pRenderer = nullptr;

	SDL_DestroyWindow(pWindow);
	pWindow = nullptr;

	SDL_Quit();
}

void InitializeAssets()
{
	// Ground body

	b2Vec2 Size		= b2Vec2(1200.0f, 30.0f);
	b2Vec2 SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	b2Vec2 Position	= b2Vec2(WINDOW_WIDTH * 0.5f, WINDOW_HEIGHT - (Size.y * 0.5f));

	b2BodyDef BodyDefinition;
	BodyDefinition.type		= b2BodyType::b2_staticBody;
	BodyDefinition.angle	= 0.0f;

	// Box2D works in meters, so convert 'Position' from a position in pixels to a position in meters
	BodyDefinition.position	= PixelToMeter(Position);

	b2PolygonShape Shape;

	// Box2D works in meters so convert 'SizeHalf' from a size in pixels to a size in meters
	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	b2FixtureDef FixtureDefinition;
	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pGroundBody = pB2World->CreateBody(&BodyDefinition);
	pGroundBody->CreateFixture(&FixtureDefinition);

	//////////////////////////////////////////////////////////////////////////

	// Left wall body

	Size		= b2Vec2(30.0f, 700.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2(SizeHalf.x, WINDOW_HEIGHT * 0.5f);

	BodyDefinition.type		= b2BodyType::b2_staticBody;
	BodyDefinition.position	= PixelToMeter(Position);
	BodyDefinition.angle	= 0.0f;

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pLeftWallBody = pB2World->CreateBody(&BodyDefinition);
	pLeftWallBody->CreateFixture(&FixtureDefinition);

	//////////////////////////////////////////////////////////////////////////

	// Right wall body

	Size		= b2Vec2(30.0f, 700.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2(WINDOW_WIDTH - SizeHalf.x, WINDOW_HEIGHT * 0.5f);

	BodyDefinition.type		= b2BodyType::b2_staticBody;
	BodyDefinition.position	= PixelToMeter(Position);
	BodyDefinition.angle	= 0.0f;

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pRightWallBody = pB2World->CreateBody(&BodyDefinition);
	pRightWallBody->CreateFixture(&FixtureDefinition);

	//////////////////////////////////////////////////////////////////////////

	// Left slope body

	Size		= b2Vec2(400.0f, 20.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2((WINDOW_WIDTH * 0.5f) - 300.0f, WINDOW_HEIGHT * 0.5f);

	BodyDefinition.type		= b2BodyType::b2_staticBody;
	BodyDefinition.position	= PixelToMeter(Position);
	BodyDefinition.angle	= DEG_TO_RAD(20.0f);

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pLeftSlopeBody = pB2World->CreateBody(&BodyDefinition);
	pLeftSlopeBody->CreateFixture(&FixtureDefinition);

	//////////////////////////////////////////////////////////////////////////

	// Right slope body

	Size		= b2Vec2(550.0f, 20.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);
	Position	= b2Vec2((WINDOW_WIDTH * 0.5f) + 300.0f, (WINDOW_HEIGHT * 0.5f) + 50.0f);

	BodyDefinition.type		= b2BodyType::b2_staticBody;
	BodyDefinition.position	= PixelToMeter(Position);
	BodyDefinition.angle	= DEG_TO_RAD(-30.0f);

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 1.0f;

	b2Body* pRightSlopeBody = pB2World->CreateBody(&BodyDefinition);
	pRightSlopeBody->CreateFixture(&FixtureDefinition);

	//////////////////////////////////////////////////////////////////////////

	Size		= b2Vec2(20.0f, 20.0f);
	SizeHalf	= b2Vec2(Size.x * 0.5f, Size.y * 0.5f);

	BodyDefinition.type		= b2BodyType::b2_dynamicBody;
	BodyDefinition.angle	= 0.0f;

	Shape.SetAsBox(PixelToMeter(SizeHalf.x), PixelToMeter(SizeHalf.y), PixelToMeter(b2Vec2(0.0f, 0.0f)), 0.0f);

	FixtureDefinition.shape			= &Shape;
	FixtureDefinition.friction		= 0.2f;
	FixtureDefinition.restitution	= 0.6f;
	FixtureDefinition.density		= 0.5f;

	for(uint32_t i = 0; i < 50; ++i)
	{
		Position = b2Vec2(100.0f + ((Size.x * 1.1f) * i), -10.0f - ((Size.y * 0.7f) * i));

		BodyDefinition.position = PixelToMeter(Position);

		b2Body* pFallingBoxBody = pB2World->CreateBody(&BodyDefinition);
		pFallingBoxBody->CreateFixture(&FixtureDefinition);
	}
}

void HandleEvents()
{
	SDL_Event Event;
	while(SDL_PollEvent(&Event))
	{
		switch(Event.type)
		{
			case SDL_QUIT:
			{
				Running = false;
				break;
			}

			default:
				break;
		}
	}
}

void Update()
{
	NewTime = (double)SDL_GetTicks();
	DeltaTime = (NewTime - OldTime) * 0.001;
	OldTime = NewTime;

	pB2World->Step((float)DeltaTime, 8, 4);
}

void Render()
{
	SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
	SDL_RenderClear(pRenderer);

	pB2World->DebugDraw();

	SDL_RenderPresent(pRenderer);
}

int main(int argc, char** argv)
{
	// Initialize SDL, create the window and the renderer, initialize Box2D etc
	if(!Initialize())
		return -1;

	InitializeAssets();

	while(Running)
	{
		HandleEvents();
		Update();
		Render();
	}

	// Shutdown/destroy everything
	Deinitialize();

	return 0;
}

If you did everything correctly, you should see the following when you start the application:

Box2D

1 Like