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. Предпочитаете видео? Вы можете посмотреть весь процесс в видео ниже.

Prerequisite: Ensure you have followed the Setting Up Box3D and Raylib in CLion with CMake and MinGW Предварительное условие: Убедитесь, что вы выполнили инструкции из руководства: Настройка Box3D и Raylib в CLion с использованием CMake и MinGW

Create a C or C++ project in CLion Создайте C или C++ проект в CLion

Click a new project button

You can select a Locaton (1) and a type of project (2) - C or C++: Вы можете выбрать расположение проекта - Locaton (1) и тип проекта (2) - C или C++:

Create a project

Let's select a C project and click the Create button: Давайте выберем проект на C и нажмем кнопку Create:

C-project

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), чтобы протестировать запуск проекта и убедиться, что всё работает:

Run and Debug buttons

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: Когда вы запустите пример выше, вы увидите следующий результат:

Result

View in the Browser


Support My Work Поддержать проект

If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.

Sberbank (Russia only) Сбербанк (только для РФ)

Sberbank SBP QR Code

Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)

+7 (917) 212-29-59

USDT TRC20

USDT TRC20 QR Code

Support via Cryptocurrency Поддержка криптовалютой

TMtY1YifNf6FKvgeFmqKGQR4NStKr3csGp