Created
November 19, 2024 13:30
-
-
Save 8Observer8/ccb951466b18e27756e6dadcf3936f24 to your computer and use it in GitHub Desktop.
A problem with float numbers using Box2D v3.1 in C and SDL3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define SDL_MAIN_USE_CALLBACKS 1 // Use callbacks instead of main() | |
#include <SDL3/SDL.h> | |
#include <SDL3/SDL_main.h> | |
#include <box2d/box2d.h> | |
#include <math.h> | |
static SDL_Window *window = NULL; | |
static SDL_Renderer *renderer = NULL; | |
b2WorldId worldId; // Physical world identifier | |
float pixelsPerMeter = 30.f; // To recalculate coordinates and sizes: world <-> pixels | |
b2DebugDraw debugDrawer; // For drawing colliders during debugging | |
b2BodyId racketBodyId; // Racket body | |
b2BodyId ballBodyId; // Ball body | |
bool keyLeft = false; // The "left" key is pressed | |
bool keyRight = false; // The right key is pressed | |
float racketSpeed = 5.f; // Racket speed | |
void initPhysicsWorld(void); | |
void drawSolidPolygon(b2Transform transform, const b2Vec2* vertices, | |
int vertexCount, float radius, b2HexColor color, void* context); | |
// ------------------------------------------------------------ | |
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) | |
{ | |
SDL_SetAppMetadata("Arkanoid using Box2D v3, SDL3, and C", "1.0", | |
"com.example.arkanoid"); | |
if (!SDL_Init(SDL_INIT_VIDEO)) { | |
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); | |
return SDL_APP_FAILURE; | |
} | |
if (!SDL_CreateWindowAndRenderer("Arkanoid using Box2D v3, SDL3, and C", | |
400, 300, 0, &window, &renderer)) | |
{ | |
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); | |
return SDL_APP_FAILURE; | |
} | |
SDL_SetRenderVSync(renderer, 1); // Turn on vertical sync | |
initPhysicsWorld(); // Initializing the physical world | |
// Create a collider line drawer for debugging | |
debugDrawer = b2DefaultDebugDraw(); | |
debugDrawer.drawShapes = true; | |
debugDrawer.DrawSolidPolygon = drawSolidPolygon; | |
return SDL_APP_CONTINUE; | |
} | |
// ------------------------------------------------------------ | |
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) | |
{ | |
if (event->type == SDL_EVENT_QUIT) { | |
return SDL_APP_SUCCESS; | |
} | |
else if (event->type == SDL_EVENT_KEY_DOWN) { | |
if (event->key.scancode == SDL_SCANCODE_LEFT) | |
keyLeft = true; | |
else if(event->key.scancode == SDL_SCANCODE_RIGHT) | |
keyRight = true; | |
} else if (event->type == SDL_EVENT_KEY_UP) { | |
if (event->key.scancode == SDL_SCANCODE_LEFT) | |
keyLeft = false; | |
else if(event->key.scancode == SDL_SCANCODE_RIGHT) | |
keyRight = false; | |
} | |
return SDL_APP_CONTINUE; | |
} | |
// ------------------------------------------------------------ | |
uint32_t lastTickTime = 0; | |
// ------------------------------------------------------------ | |
SDL_AppResult SDL_AppIterate(void *appstate) | |
{ | |
uint32_t currentTickTime = SDL_GetTicks(); | |
float deltaTime = (currentTickTime - lastTickTime) / 1000.f; | |
lastTickTime = currentTickTime; | |
// Racket control | |
if (keyLeft) { | |
float x = b2Body_GetPosition(racketBodyId).x; | |
float y = b2Body_GetPosition(racketBodyId).y; | |
x = x - racketSpeed * deltaTime; | |
if (x * pixelsPerMeter > 50) { | |
b2Body_SetTransform(racketBodyId, (b2Vec2){x, y}, b2MakeRot(0.f)); | |
// b2Body_SetTransform(racketBodyId, (b2Vec2){x, y}, (b2Rot){1.f, 0.f}); | |
} | |
} else if (keyRight) { | |
float x = b2Body_GetPosition(racketBodyId).x; | |
float y = b2Body_GetPosition(racketBodyId).y; | |
x = x + racketSpeed * deltaTime; | |
if (x * pixelsPerMeter < 350) { | |
b2Body_SetTransform(racketBodyId, (b2Vec2){x, y}, b2MakeRot(0.f)); | |
} | |
} | |
b2Vec2 velBefore = b2Body_GetLinearVelocity(ballBodyId); | |
b2World_Step(worldId, 0.016, 5); // Make a time step of 0.016 sec | |
b2ContactData contactData[5]; | |
if (b2Body_GetContactData(ballBodyId, contactData, 5)) | |
{ | |
b2ContactEvents contactEvents = b2World_GetContactEvents(worldId); | |
for (int i = 0; i < contactEvents.beginCount; ++i) { | |
b2ContactBeginTouchEvent touchEvent = contactEvents.beginEvents[i]; | |
char *nameA = (char*)b2Shape_GetUserData(touchEvent.shapeIdA); | |
char *nameB = (char*)b2Shape_GetUserData(touchEvent.shapeIdB); | |
if (nameA && nameB) | |
{ | |
if (strcmp(nameA, "block") == 0 && strcmp(nameB, "ball") == 0) { | |
b2Vec2 v = b2Body_GetLinearVelocity(ballBodyId); | |
if (fabs(v.x - velBefore.x) > 0.001f || fabs(v.y - velBefore.y) > 0.001f) { | |
b2BodyId blockBodyId = b2Shape_GetBody(touchEvent.shapeIdA); | |
b2DestroyBody(blockBodyId); | |
} | |
} else if (strcmp(nameA, "ball") == 0 && strcmp(nameB, "block") == 0) { | |
b2Vec2 v = b2Body_GetLinearVelocity(ballBodyId); | |
if (fabs(v.x - velBefore.x) > 0.001f || fabs(v.y - velBefore.y) > 0.001f) { | |
b2BodyId blockBodyId = b2Shape_GetBody(touchEvent.shapeIdB); | |
b2DestroyBody(blockBodyId); | |
} | |
} | |
} | |
} | |
} | |
SDL_SetRenderDrawColor(renderer, 33, 33, 33, SDL_ALPHA_OPAQUE); // Canvas color | |
SDL_RenderClear(renderer); // Clear the canvas and fill it with the canvas color | |
b2World_Draw(worldId, &debugDrawer); // Drawing collider lines for debugging | |
SDL_RenderPresent(renderer); // Display the contents of the drawer on the screen | |
return SDL_APP_CONTINUE; // Continue running the program! | |
} | |
// ------------------------------------------------------------ | |
void SDL_AppQuit(void *appstate, SDL_AppResult result) | |
{ | |
// window and renderer will be automatically removed | |
// Deleting the physical world will also delete bodies and shapes | |
b2DestroyWorld(worldId); | |
worldId = b2_nullWorldId; | |
} | |
// ------------------------------------------------------------ | |
void initPhysicsWorld(void) | |
{ | |
// Create a physical world with zero gravity | |
b2Vec2 gravity = { 0.f, 0.f }; | |
b2WorldDef worldDef = b2DefaultWorldDef(); | |
worldDef.gravity = gravity; | |
worldId = b2CreateWorld(&worldDef); | |
// Create walls | |
// Left wall | |
b2BodyDef leftWallBodyDef = b2DefaultBodyDef(); | |
leftWallBodyDef.type = b2_staticBody; | |
leftWallBodyDef.position = (b2Vec2){ 10.f / pixelsPerMeter, 150.f / pixelsPerMeter }; | |
b2BodyId leftWallBodyId = b2CreateBody(worldId, &leftWallBodyDef); | |
// Left wall shape | |
b2Polygon leftWallShape = b2MakeBox(10.f / pixelsPerMeter, 150.f / pixelsPerMeter); | |
b2ShapeDef leftWallShapeDef = b2DefaultShapeDef(); | |
leftWallShapeDef.friction = 0.f; | |
b2CreatePolygonShape(leftWallBodyId, &leftWallShapeDef, &leftWallShape); | |
// Right wall | |
b2BodyDef rightWallBodyDef = b2DefaultBodyDef(); | |
rightWallBodyDef.type = b2_staticBody; | |
rightWallBodyDef.position = (b2Vec2){ 390.f / pixelsPerMeter, 150.f / pixelsPerMeter }; | |
b2BodyId rightWallBodyId = b2CreateBody(worldId, &rightWallBodyDef); | |
// Right wall shape | |
b2Polygon rightWallShape = b2MakeBox(10.f / pixelsPerMeter, 150.f / pixelsPerMeter); | |
b2ShapeDef rightWallShapeDef = b2DefaultShapeDef(); | |
rightWallShapeDef.friction = 0.f; | |
b2CreatePolygonShape(rightWallBodyId, &rightWallShapeDef, &rightWallShape); | |
// Top wall | |
b2BodyDef topWallBodyDef = b2DefaultBodyDef(); | |
topWallBodyDef.type = b2_staticBody; | |
topWallBodyDef.position = (b2Vec2){ 200.f / pixelsPerMeter, 10.f / pixelsPerMeter }; | |
b2BodyId topWallBodyId = b2CreateBody(worldId, &topWallBodyDef); | |
// Top wall shape | |
b2Polygon topWallShape = b2MakeBox(180.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
b2ShapeDef topWallShapeDef = b2DefaultShapeDef(); | |
topWallShapeDef.friction = 0.f; | |
b2CreatePolygonShape(topWallBodyId, &topWallShapeDef, &topWallShape); | |
// Create a racket | |
b2BodyDef racketBodyDef = b2DefaultBodyDef(); | |
racketBodyDef.type = b2_kinematicBody; | |
racketBodyDef.position = (b2Vec2){ 200.f / pixelsPerMeter, 285.f / pixelsPerMeter }; | |
racketBodyId = b2CreateBody(worldId, &racketBodyDef); | |
// Racket shape | |
b2Polygon racketShape = b2MakeBox(30.f / pixelsPerMeter, 5.f / pixelsPerMeter); | |
b2ShapeDef racketShapeDef = b2DefaultShapeDef(); | |
racketShapeDef.friction = 0.f; | |
b2ShapeId racketShapeId = b2CreatePolygonShape(racketBodyId, &racketShapeDef, &racketShape); | |
b2Shape_SetUserData(racketShapeId, "racket"); | |
// Create a ball | |
b2BodyDef ballBodyDef = b2DefaultBodyDef(); | |
ballBodyDef.type = b2_dynamicBody; | |
ballBodyDef.position = (b2Vec2){200.f / pixelsPerMeter, 275.f / pixelsPerMeter}; | |
ballBodyId = b2CreateBody(worldId, &ballBodyDef); | |
// Ball shape | |
b2Polygon ballShape = b2MakeBox(5.f / pixelsPerMeter, 5.f / pixelsPerMeter); | |
b2ShapeDef ballShapeDef = b2DefaultShapeDef(); | |
ballShapeDef.friction = 0.f; | |
ballShapeDef.restitution = 1.f; | |
b2ShapeId ballShapeId = b2CreatePolygonShape(ballBodyId, &ballShapeDef, &ballShape); | |
b2Body_SetLinearVelocity(ballBodyId, (b2Vec2){0.f, -5.f}); // Начальная скорость | |
b2Body_SetFixedRotation(ballBodyId, true); // Запрещаем вращение | |
// Store the name of the object in its physical body | |
// strcpy(ballUserData.name, "ball"); | |
// b2Body_SetUserData(ballBodyId, &ballUserData); | |
b2Shape_SetUserData(ballShapeId, "ball"); // myEntityInfo | |
// Create the first block | |
b2BodyDef block0BodyDef = b2DefaultBodyDef(); | |
block0BodyDef.type = b2_staticBody; | |
block0BodyDef.position = (b2Vec2){ 175.f / pixelsPerMeter, 50.f / pixelsPerMeter }; | |
b2BodyId block0BodyId = b2CreateBody(worldId, &block0BodyDef); | |
// Block shape | |
b2Polygon block0Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
b2ShapeDef block0ShapeDef = b2DefaultShapeDef(); | |
block0ShapeDef.friction = 0.f; | |
b2ShapeId block0ShapeId = b2CreatePolygonShape(block0BodyId, &block0ShapeDef, &block0Shape); | |
b2Shape_SetUserData(block0ShapeId, "block"); | |
// Create the second block | |
// b2BodyDef block1BodyDef = b2DefaultBodyDef(); | |
// block1BodyDef.type = b2_staticBody; | |
// block1BodyDef.position = (b2Vec2){ 225.f / pixelsPerMeter, 50.f / pixelsPerMeter }; | |
// b2BodyId block1BodyId = b2CreateBody(worldId, &block1BodyDef); | |
// // Block shape | |
// b2Polygon block1Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
// b2ShapeDef block1ShapeDef = b2DefaultShapeDef(); | |
// block1ShapeDef.friction = 0.f; | |
// b2ShapeId block1ShapeId = b2CreatePolygonShape(block1BodyId, &block1ShapeDef, &block1Shape); | |
// b2Shape_SetUserData(block1ShapeId, "block"); | |
// Create the third block | |
b2BodyDef block2BodyDef = b2DefaultBodyDef(); | |
block2BodyDef.type = b2_staticBody; | |
block2BodyDef.position = (b2Vec2){ 175.f / pixelsPerMeter, 100.f / pixelsPerMeter }; | |
b2BodyId block2BodyId = b2CreateBody(worldId, &block2BodyDef); | |
// Block shape | |
b2Polygon block2Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
b2ShapeDef block2ShapeDef = b2DefaultShapeDef(); | |
block2ShapeDef.friction = 0.f; | |
b2ShapeId block2ShapeId = b2CreatePolygonShape(block2BodyId, &block2ShapeDef, &block2Shape); | |
b2Shape_SetUserData(block2ShapeId, "block"); | |
// Create the fourth block | |
// b2BodyDef block3BodyDef = b2DefaultBodyDef(); | |
// block3BodyDef.type = b2_staticBody; | |
// block3BodyDef.position = (b2Vec2){ 225.f / pixelsPerMeter, 100.f / pixelsPerMeter }; | |
// b2BodyId block3BodyId = b2CreateBody(worldId, &block3BodyDef); | |
// // Block shape | |
// b2Polygon block3Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
// b2ShapeDef block3ShapeDef = b2DefaultShapeDef(); | |
// block3ShapeDef.friction = 0.f; | |
// b2ShapeId block3ShapeId = b2CreatePolygonShape(block3BodyId, &block3ShapeDef, &block3Shape); | |
// b2Shape_SetUserData(block3ShapeId, "block"); | |
// Create the fifth block | |
b2BodyDef block4BodyDef = b2DefaultBodyDef(); | |
block4BodyDef.type = b2_staticBody; | |
block4BodyDef.position = (b2Vec2){ 175.f / pixelsPerMeter, 150.f / pixelsPerMeter }; | |
b2BodyId block4BodyId = b2CreateBody(worldId, &block4BodyDef); | |
// Block shape | |
b2Polygon block4Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
b2ShapeDef block4ShapeDef = b2DefaultShapeDef(); | |
block4ShapeDef.friction = 0.f; | |
b2ShapeId block4ShapeId = b2CreatePolygonShape(block4BodyId, &block4ShapeDef, &block4Shape); | |
b2Shape_SetUserData(block4ShapeId, "block"); | |
// Create the sixth block | |
// b2BodyDef block5BodyDef = b2DefaultBodyDef(); | |
// block5BodyDef.type = b2_staticBody; | |
// block5BodyDef.position = (b2Vec2){ 225.f / pixelsPerMeter, 150.f / pixelsPerMeter }; | |
// b2BodyId block5BodyId = b2CreateBody(worldId, &block5BodyDef); | |
// // Block shape | |
// b2Polygon block5Shape = b2MakeBox(20.f / pixelsPerMeter, 10.f / pixelsPerMeter); | |
// b2ShapeDef block5ShapeDef = b2DefaultShapeDef(); | |
// block5ShapeDef.friction = 0.f; | |
// b2ShapeId block5ShapeId = b2CreatePolygonShape(block5BodyId, &block5ShapeDef, &block5Shape); | |
// b2Shape_SetUserData(block5ShapeId, "block"); | |
} | |
// ------------------------------------------------------------ | |
// Drawing lines around Box2D colliders for debugging | |
void drawSolidPolygon(b2Transform transform, const b2Vec2* vertices, | |
int vertexCount, float radius, b2HexColor color, void* context) | |
{ | |
// Get the pixel format | |
SDL_Surface *surface = SDL_GetWindowSurface(window); | |
const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(surface->format); | |
// Extract RGB | |
Uint8 r, g, b; | |
SDL_GetRGB(color, format, NULL, &r, &g, &b); | |
// Draw a collider rectangle with lines | |
SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE); | |
for (int i = 0; i < vertexCount; ++i) { | |
int next_index = (i + 1 == vertexCount) ? 0 : i + 1; | |
b2Vec2 p0 = b2TransformPoint(transform, vertices[i]); | |
b2Vec2 p1 = b2TransformPoint(transform, vertices[next_index]); | |
float x0 = p0.x * pixelsPerMeter; | |
float y0 = p0.y * pixelsPerMeter; | |
float x1 = p1.x * pixelsPerMeter; | |
float y1 = p1.y * pixelsPerMeter; | |
SDL_RenderLine(renderer, x0, y0, x1, y1); | |
} | |
} | |
// ------------------------------------------------------------ |
Author
8Observer8
commented
Nov 19, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment