Using Debug Drawer (b3DebugDraw) with Box3D and Raylib in CLion using CMake and MinGW Использование Debug Drawer (b3DebugDraw) для Box3D и Raylib в CLion с использованием CMake и MinGW
Prefer video? You can watch the entire process in the video below. Предпочитаете видео? Вы можете посмотреть весь процесс в видео ниже.
Create a C or C++ project in CLion Создайте C или C++ проект в CLion
-
Run CLion and click on the
New Project button:
Запустите CLion и нажмите на кнопку New Project:
You can select a Locaton (1) and a type of project (2) - C or C++: Вы можете выбрать расположение проекта - Locaton (1) и тип проекта (2) - C или C++:
Let's select a C project and click the Create button:
Давайте выберем проект на C и нажмем кнопку Create:
You can click the Run (1) or Debug (2) button to test the project launch and ensure that everything is working:
Вы можете нажать кнопку Run (1) или Debug (2), чтобы протестировать запуск проекта и убедиться, что всё работает:
Setting up Box3D and Raylib in the CMakeLists.txt file
Настройка Box3D и Raylib в файле CMakeLists.txt
Replace the contents of the CMakeLists.txt file with the following code:
Замените содержимое файла CMakeLists.txt следующим кодом:
set(CMAKE_BUILD_TYPE "Debug")
cmake_minimum_required(VERSION 3.21)
project(debug-drawer-box3d-raylib-c)
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(app)
target_include_directories(app PRIVATE C:/libs/raylib-6.0_win64_mingw-w64/include)
target_link_directories(app PRIVATE C:/libs/raylib-6.0_win64_mingw-w64/lib)
target_include_directories(app PRIVATE C:/libs/box3d-0.1.0-mingw-winlibs-gcc-16.1/include)
target_link_directories(app PRIVATE C:/libs/box3d-0.1.0-mingw-winlibs-gcc-16.1/lib)
target_link_libraries(app PRIVATE box3d raylib winmm)
target_link_options(app PRIVATE -static)
target_sources(app
PRIVATE
main.c
)
Replace the contents of the main.c file with the following code:
Замените содержимое файла main.c следующим кодом:
#include <box3d/box3d.h>
#include <raylib.h>
#include <raymath.h> // Required for QuaternionToMatrix and MatrixToFloat
#include <rlgl.h> // Required for rlPushMatrix, rlMultMatrixf, rlPopMatrix
#include <stdio.h>
#include <stdlib.h> // Required for malloc/free
// Global or persistent data
b3WorldId worldId;
b3DebugDraw dd;
Camera3D camera = { 0 };
typedef struct
{
b3ShapeType type;
b3Vec3 extents; // For boxes, we store half-extents
float radius; // For spheres
} MyDebugShape;
int b3InternalAssert(const char *condition, const char *fileName, int lineNumber)
{
printf("Box3D Assertion Failed: %s, file %s, line %d\n", condition, fileName, lineNumber);
fflush(stdout);
// Returning 1 usually tells the engine to trigger a breakpoint (if supported)
// Returning 0 just ignores the error.
// Using abort() here ensures the program stops immediately so you can debug.
abort();
return 1;
}
void *createDebugShape(const b3DebugShape *debugShape, void *context)
{
MyDebugShape *myShape = (MyDebugShape *)malloc(sizeof(MyDebugShape));
myShape->type = debugShape->type;
if (debugShape->type == b3_hullShape)
{
b3AABB aabb = debugShape->hull->aabb;
// Calculate the full dimensions (width, height, depth)
// size = upperBound - lowerBound
myShape->extents.x = (aabb.upperBound.x - aabb.lowerBound.x) * 0.5f;
myShape->extents.y = (aabb.upperBound.y - aabb.lowerBound.y) * 0.5f;
myShape->extents.z = (aabb.upperBound.z - aabb.lowerBound.z) * 0.5f;
}
else if (debugShape->type == b3_sphereShape)
{
myShape->radius = debugShape->sphere->radius;
}
return (void *)myShape;
}
void destroyDebugShape(void *userShape, void *context)
{
free(userShape); // Free the memory we allocated
}
bool drawShape(void *userShape, b3WorldTransform transform, b3HexColor color, void *context)
{
MyDebugShape *myShape = (MyDebugShape *)userShape;
b3Vec3 pos = b3ToVec3(transform.p); // Convert high-precision b3Pos to float Vector3
Vector3 vPos = { pos.x, pos.y, pos.z };
if (myShape->type == b3_hullShape)
{
// Map b3Quat (s, v) to Raylib Quaternion (x, y, z, w)
// Raylib expects: x, y, z, w
// Box3D provides: v.x, v.y, v.z, s
Matrix mat = QuaternionToMatrix((Quaternion) {
transform.q.v.x,
transform.q.v.y,
transform.q.v.z,
transform.q.s });
// Apply translation to the matrix
mat.m12 = pos.x;
mat.m13 = pos.y;
mat.m14 = pos.z;
// Draw with matrix
rlPushMatrix();
rlMultMatrixf(MatrixToFloat(mat));
Vector3 size = { myShape->extents.x * 2.0f, myShape->extents.y * 2.0f, myShape->extents.z * 2.0f };
DrawCubeWires((Vector3) { 0, 0, 0 }, size.x, size.y, size.z, DARKGRAY);
rlPopMatrix();
}
else if (myShape->type == b3_sphereShape) // Add this
{
// Create the rotation matrix from the quaternion
Matrix mat = QuaternionToMatrix((Quaternion) {
transform.q.v.x,
transform.q.v.y,
transform.q.v.z,
transform.q.s });
// Apply the translation
mat.m12 = pos.x;
mat.m13 = pos.y;
mat.m14 = pos.z;
// Apply the matrix so the sphere's coordinate system is transformed
rlPushMatrix();
rlMultMatrixf(MatrixToFloat(mat));
// Draw the sphere wires at origin (0,0,0) because the matrix handles position
DrawSphereWires((Vector3) { 0, 0, 0 }, myShape->radius, 10, 10, DARKGRAY);
rlPopMatrix();
}
return true;
}
void CreateBoxAtMouse()
{
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
{
// Get a ray from the camera to the mouse position
Ray ray = GetMouseRay(GetMousePosition(), camera);
// Find intersection with the Z = 0 plane.
// The ray is: origin + t * direction
// We want origin.z + t * direction.z = 0
// So: t = -origin.z / direction.z
float t = -ray.position.z / ray.direction.z;
Vector3 worldPos = {
ray.position.x + ray.direction.x * t,
ray.position.y + ray.direction.y * t,
0.0f // Fixed on the Z=0 plane
};
// Create the Box3D body
b3BodyDef boxBodyDef = b3DefaultBodyDef();
boxBodyDef.type = b3_dynamicBody;
boxBodyDef.position = (b3Vec3) { worldPos.x, worldPos.y, worldPos.z };
b3BodyId boxId = b3CreateBody(worldId, &boxBodyDef);
b3BoxHull boxHull = b3MakeBoxHull(0.5f, 0.5f, 0.5f);
b3ShapeDef boxShapeDef = b3DefaultShapeDef();
boxShapeDef.density = 1.0f;
b3CreateHullShape(boxId, &boxShapeDef, &boxHull.base);
}
}
void CreateSphereAtMouse()
{
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT))
{
Ray ray = GetMouseRay(GetMousePosition(), camera);
float t = -ray.position.z / ray.direction.z;
Vector3 worldPos = {
ray.position.x + ray.direction.x * t,
ray.position.y + ray.direction.y * t,
0.0f
};
b3BodyDef bodyDef = b3DefaultBodyDef();
bodyDef.type = b3_dynamicBody;
bodyDef.position = (b3Vec3) { worldPos.x, worldPos.y, worldPos.z };
b3BodyId bodyId = b3CreateBody(worldId, &bodyDef);
// Use the sphere primitive constructor
b3Sphere sphere = { .center = { 0, 0, 0 }, .radius = 0.5f };
b3ShapeDef shapeDef = b3DefaultShapeDef();
shapeDef.density = 1.0f;
b3CreateSphereShape(bodyId, &shapeDef, &sphere);
}
}
void InitGame()
{
InitWindow(800, 450, "Raylib and Box3D");
SetTargetFPS(60);
// Camera
camera.position = (Vector3) { 0.f, 0.f, 10.f };
camera.target = (Vector3) { 0.f, 0.f, 0.f };
camera.up = (Vector3) { 0.f, 1.f, 0.f };
camera.fovy = 50.f;
camera.projection = CAMERA_PERSPECTIVE;
// Debug draw
dd = b3DefaultDebugDraw();
dd.DrawShapeFcn = drawShape;
dd.drawShapes = true;
dd.drawingBounds = (b3AABB) {
(b3Vec3) { -100.0f, -100.0f, -100.0f },
(b3Vec3) { 100.0f, 100.0f, 100.0f }
};
// Box3D world
b3WorldDef worldDef = b3DefaultWorldDef();
worldDef.gravity = (b3Vec3) { 0.0f, -9.8f, 0.0f };
worldDef.createDebugShape = createDebugShape;
worldDef.destroyDebugShape = destroyDebugShape;
worldId = b3CreateWorld(&worldDef);
// Ground rotation
// Convert 20 degrees to radians
float angleInRadians = 10.0f * (PI / 180.0f);
// Define the axis of rotation (Z-axis)
b3Vec3 axis = { 0.f, 0.f, 1.f };
// Create the quaternion
b3Quat rotation = b3MakeQuatFromAxisAngle(axis, angleInRadians);
// Create a ground
b3BodyDef groundBodyDef = b3DefaultBodyDef();
groundBodyDef.position = (b3Vec3) { 0.f, -2.f, 0.f };
groundBodyDef.rotation = rotation;
b3BodyId groundId = b3CreateBody(worldId, &groundBodyDef);
b3BoxHull groundBox = b3MakeBoxHull(5.f, 0.2f, 1.f);
b3ShapeDef groundShapeDef = b3DefaultShapeDef();
b3CreateHullShape(groundId, &groundShapeDef, &groundBox.base);
// Create a dynamic falling box
b3BodyDef boxBodyDef = b3DefaultBodyDef();
boxBodyDef.type = b3_dynamicBody; // This tells Box3D it should fall
boxBodyDef.position = (b3Vec3) { 0.f, 5.f, 0.f }; // Start 5 units above
b3BodyId boxId = b3CreateBody(worldId, &boxBodyDef);
b3BoxHull boxHull = b3MakeBoxHull(0.5f, 0.5f, 0.5f);
b3ShapeDef boxShapeDef = b3DefaultShapeDef();
// Setting density makes the object have mass so gravity affects it
boxShapeDef.density = 1.0f;
b3CreateHullShape(boxId, &boxShapeDef, &boxHull.base);
}
void UpdateGame()
{
CreateBoxAtMouse();
CreateSphereAtMouse();
b3World_Step(worldId, 1.0f / 60.0f, 5);
}
void DrawGame()
{
BeginDrawing();
ClearBackground(RAYWHITE);
BeginMode3D(camera);
b3World_Draw(worldId, &dd, B3_DEFAULT_MASK_BITS);
EndMode3D();
// Display game information
b3Vec3 g = b3World_GetGravity(worldId);
DrawText(TextFormat("Gravity is: (%0.1f, %0.1f, %0.1f)", g.x, g.y, g.z), 10, 10, 20, DARKGRAY);
// Display controls
DrawText("Controls: [Left Click] Spawn Box | [Right Click] Spawn Sphere", 10, 40, 20, MAROON);
EndDrawing();
}
void ShutdownGame()
{
b3DestroyWorld(worldId);
CloseWindow();
}
int main()
{
InitGame();
while (!WindowShouldClose())
{
UpdateGame();
DrawGame();
}
ShutdownGame();
return 0;
}
When you run the example above, you will see the following result: Когда вы запустите пример выше, вы увидите следующий результат:
Support My Work Поддержать проект
If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.
Sberbank (Russia only) Сбербанк (только для РФ)
Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)
USDT TRC20
Support via Cryptocurrency Поддержка криптовалютой