erincatto / box2d

Box2D is a 2D physics engine for games
https://box2d.org
MIT License
7.44k stars 1.47k forks source link

DrawSegment is not called #725

Open 8Observer8 opened 1 year ago

8Observer8 commented 1 year ago

I already have created three simple examples in: Qt6, PyQt6, and SDL2. I thought that a problem in Qt6 but it the same for three cases. It looks like a bug. These examples try to print hello in the console from DrawSegment().

I expect to see hello in the console:

DebugDrawer.cpp

#include "DebugDrawer.h"
#include <iostream>

DebugDrawer::DebugDrawer()
{

}

void DebugDrawer::DrawSegment(const b2Vec2 &p1, const b2Vec2 &p2, const b2Color &color)
{
    std::cout << "hello" << std::endl;
}

void DebugDrawer::DrawSolidPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) { }
void DebugDrawer::DrawPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) { }
void DebugDrawer::DrawPoint(const b2Vec2 &p, float size, const b2Color &color) { }
void DebugDrawer::DrawCircle(const b2Vec2 &center, float radius, const b2Color &color) { }
void DebugDrawer::DrawSolidCircle(const b2Vec2 &center, float radius, const b2Vec2 &axis, const b2Color &color) { }
void DebugDrawer::DrawTransform(const b2Transform &xf) { }

I inherited from the b2Draw and overloaded the methods:

DebugDrawer.h

#ifndef DEBUGDRAWER_H
#define DEBUGDRAWER_H

#include "box2d/b2_draw.h"

class DebugDrawer : public b2Draw
{
public:
    DebugDrawer();

private:
    void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);
    void DrawPoint (const b2Vec2 &p, float size, const b2Color &color);
    void DrawPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color);
    void DrawCircle(const b2Vec2& center, float radius, const b2Color& color);
    void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color);
    void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color);
    void DrawTransform(const b2Transform& xf);
};

#endif // DEBUGDRAWER_H

I created an object with the box shape:

    b2PolygonShape shape;
    shape.SetAsBox(50.f / WORLD_SCALE, 50.f / WORLD_SCALE);

    b2BodyDef bdef;
    bdef.type = b2_staticBody;

    pBody = pWorld->CreateBody(&bdef);
    pBody->CreateFixture(&shape, 2.f);

I set the debug draw in the main.cpp file:

    pDebugDrawer = new DebugDrawer();
    pWorld->SetDebugDraw(pDebugDrawer);

I set flags:

   uint32 flags = 0;
    flags += b2Draw::e_shapeBit;
    flags += b2Draw::e_jointBit;
    flags += b2Draw::e_centerOfMassBit;
    flags += b2Draw::e_aabbBit;
    flags += b2Draw::e_pairBit;
    pDebugDrawer->SetFlags(flags);

I call pWorld.Step() and pWorld->DebugDraw():

        pWorld->Step(0.016f, 8, 3);
        pWorld->DebugDraw();

main.cpp

#ifdef _WIN32
#include <windows.h>
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
#endif

#define SDL_MAIN_HANDLED

#include <glad/glad.h>
#include <SDL.h>
#include <box2d/box2d.h>
#include <iostream>
#include "DebugDrawer.h"

const float WORLD_SCALE = 30.f;
b2World* pWorld;
DebugDrawer* pDebugDrawer;
b2Body* pBody;

int main()
{
    // Initialize the SDL2 library
    if (SDL_Init(SDL_INIT_VIDEO) != 0)
    {
        SDL_Log("Failed to initialize the SDL2 library: %s", SDL_GetError());
        return 1;
    }

    // Create a SDL window
    const int WIN_WIDTH = 500;
    const int WIN_HEIGHT = 500;
    SDL_Window* window = SDL_CreateWindow(
        "Empty Window",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        WIN_WIDTH, WIN_HEIGHT,
        SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    // Create a SDL renderer
    SDL_Renderer* renderer = SDL_CreateRenderer(
        window, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer)
    {
        SDL_Log("Failed to create a SDL renderer %s", SDL_GetError());
        return 1;
    }

    SDL_GLContext context = SDL_GL_CreateContext(window);

    // Initialize the GLAD library
    if (!gladLoadGL())
    {
        SDL_Log("Failed to initialize the GLAD library");
        return 1;
    }

    glViewport(0, 0, WIN_WIDTH, WIN_HEIGHT);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

    b2Vec2 gravity(0.f, 9.8f);
    pWorld = new b2World(gravity);

    pDebugDrawer = new DebugDrawer();
    pWorld->SetDebugDraw(pDebugDrawer);

    uint32 flags = 0;
    flags += b2Draw::e_shapeBit;
    flags += b2Draw::e_jointBit;
    flags += b2Draw::e_centerOfMassBit;
    flags += b2Draw::e_aabbBit;
    flags += b2Draw::e_pairBit;
    pDebugDrawer->SetFlags(flags);

    b2PolygonShape shape;
    shape.SetAsBox(50.f / WORLD_SCALE, 50.f / WORLD_SCALE);

    b2BodyDef bdef;
    bdef.type = b2_staticBody;

    pBody = pWorld->CreateBody(&bdef);
    pBody->CreateFixture(&shape, 2.f);

    SDL_Event event;
    bool running = true;
    SDL_PollEvent(&event);

    while (running)
    {
        switch (event.type)
        {
        case SDL_QUIT:
            running = false;
            break;
        case SDL_KEYDOWN:
            switch (event.key.keysym.sym)
            {
            case SDLK_ESCAPE:
                running = false;
                break;
            }
            break;
        }

        glClear(GL_COLOR_BUFFER_BIT);

        pWorld->Step(0.016f, 8, 3);
        pWorld->DebugDraw();

        SDL_GL_SwapWindow(window);
        SDL_PollEvent(&event);
    }

    delete pWorld;
    delete pDebugDrawer;

    SDL_GL_DeleteContext(context);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
8Observer8 commented 1 year ago

The same example in PyQt6:

main.py

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QSurfaceFormat
from PyQt6.QtWidgets import QApplication

from widget import Widget

def main():
    QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL)
    app = QApplication(sys.argv)

    format = QSurfaceFormat()
    format.setSamples(8)

    w = Widget()
    w.setFormat(format)
    w.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

widget.py

from Box2D import (b2_staticBody, b2Body, b2BodyDef, b2FixtureDef,
                   b2PolygonShape, b2Vec2, b2World)
from OpenGL import GL as gl
from PyQt6.QtCore import QElapsedTimer, QSize, QTimer
from PyQt6.QtOpenGLWidgets import QOpenGLWidget

from debug_drawer import DebugDrawer

class Widget(QOpenGLWidget):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Box2D, OpenGL3, PyQt6")
        self.setFixedSize(QSize(500, 500))
        self.deltaTime = 0

        self.WORLD_SCALE = 30.0
        self.world = b2World(gravity=b2Vec2(0.0, 9.8))

    def initializeGL(self):
        gl.glClearColor(0.2, 0.2, 0.2, 1.0)
        gl.glEnable(gl.GL_DEPTH_TEST)

        self.debugDrawer = DebugDrawer()
        self.world.renderer = self.debugDrawer

        self.debugDrawer.flags = { 'drawShapes': True,
            'drawJoints': True, 'drawAABBs': True, 'drawPairs': True }
        # print(self.debugDrawer.flags)

        shape = b2PolygonShape()
        shape.SetAsBox(50.0 / self.WORLD_SCALE, 50.0 / self.WORLD_SCALE)

        bodyDef = b2BodyDef()
        bodyDef.type = b2_staticBody

        self.body: b2Body = self.world.CreateBody(bodyDef)
        fixtureDef = b2FixtureDef()
        fixtureDef.shape = shape
        fixtureDef.density = 2
        self.body.CreateFixture(fixtureDef)

        self.timer = QTimer()
        self.timer.timeout.connect(self.animationLoop)
        self.elapsedTimer = QElapsedTimer()
        self.elapsedTimer.start()
        self.timer.start(1000//60)

    def paintGL(self):
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        self.world.DrawDebugData()

    def resizeGL(self, w: int, h: int):
        gl.glViewport(0, 0, w, h)

    def animationLoop(self):
        self.deltaTime = self.elapsedTimer.elapsed() / 1000.0
        self.elapsedTimer.restart()
        self.world.Step(self.deltaTime, 8, 3)
        self.update()

debug_drawer.py

from Box2D import b2Draw

class DebugDrawer(b2Draw):

    def DrawSegment(self, p1, p2, color):
        print("hello")

    def DrawSolidPolygon(self, vertices, color):
        pass
    def DrawPoint(self, p, size, color):
        pass
    def DrawPolygon(self, vertices, color):
        pass
    def DrawCircle(self, center, radius, color, drawwidth=1):
        pass
    def DrawSolidCircle(self, center, radius, axis, color):
        pass
    def DrawTransform(self, xf):
        pass
8Observer8 commented 1 year ago

The same example in Qt6 and C++:

main.cpp

#ifdef _WIN32
#include <windows.h>
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
#endif

#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QApplication>

#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSurfaceFormat format;
    format.setSamples(8);

    Widget w;
    w.setFormat(format);
    w.show();
    return a.exec();
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include "box2d/box2d.h"
#include <QtCore/QElapsedTimer>
#include <QtCore/QTimer>
#include <QtOpenGLWidgets/QOpenGLWidget>
#include <QtGui/QOpenGLFunctions>

#include "DebugDrawer.h"

class Widget : public QOpenGLWidget, QOpenGLFunctions
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void animationLoop();

private:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;

private:
    const float WORLD_SCALE = 30.f;
    b2World *m_pWorld;
    DebugDrawer *m_pDebugDrawer;
    b2Body *m_pBody;

    QElapsedTimer m_elapsedTimer;
    QTimer m_timer;
    float m_deltaTime;
};
#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include <QtCore/QDebug>

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setWindowTitle("Box2D, OpenGL3, Qt6, C++");
    setFixedSize(QSize(500, 500));

    b2Vec2 gravity(0.f, 9.8f);
    m_pWorld = new b2World(gravity);
}

Widget::~Widget()
{
    delete m_pWorld;
    delete m_pDebugDrawer;
}

void Widget::initializeGL()
{
    initializeOpenGLFunctions();

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);
    glEnable(GL_DEPTH_TEST);

    m_pDebugDrawer = new DebugDrawer();
    m_pWorld->SetDebugDraw(m_pDebugDrawer);

    uint32 flags = 0;
    flags += b2Draw::e_shapeBit;
    flags += b2Draw::e_jointBit;
    flags += b2Draw::e_centerOfMassBit;
    flags += b2Draw::e_aabbBit;
    flags += b2Draw::e_pairBit;
    m_pDebugDrawer->SetFlags(flags);
//    m_pDebugDrawer->SetFlags(b2Draw::e_shapeBit);

    b2PolygonShape shape;
    shape.SetAsBox(50.f / WORLD_SCALE, 50.f / WORLD_SCALE);

    b2BodyDef bdef;
    bdef.type = b2_staticBody;

    m_pBody = m_pWorld->CreateBody(&bdef);
    m_pBody->CreateFixture(&shape, 2.f);

    connect(&m_timer, &QTimer::timeout, this, &Widget::animationLoop);
    m_timer.start(1000.f/60.f);
    m_elapsedTimer.start();
}

void Widget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_pWorld->DebugDraw();
}

void Widget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void Widget::animationLoop()
{
    m_deltaTime = m_elapsedTimer.elapsed() / 1000.f;
    m_elapsedTimer.restart();
    m_pWorld->Step(m_deltaTime, 8, 3);
    update();
}

.pro

QT       += core gui openglwidgets

win32: LIBS += -lopengl32

INCLUDEPATH += "E:\Libs\box2d-2.4.1-mingw-64-bit\include"
LIBS += -L"E:\Libs\box2d-2.4.1-mingw-64-bit\lib"
LIBS += -lbox2d

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    DebugDrawer.cpp \
    main.cpp \
    Widget.cpp

HEADERS += \
    DebugDrawer.h \
    Widget.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
8Observer8 commented 1 year ago

I should use DrawPolygon to draw segments of colliders when I use boxes to draw borders around game objects. DrawSegment() will be called when an instance of b2EdgeShape is created:

C++:

    b2EdgeShape edgeShape;
    edgeShape.SetOneSided(b2Vec2(0.f, 0.f), b2Vec2(1.f, 0.f),
                          b2Vec2(2.f, 0.f), b2Vec2(3.f, 0.f));
    m_pEdgeBody = m_pWorld->CreateBody(&bdef);
    m_pEdgeBody->CreateFixture(&edgeShape, 2.f);

Python:

        edgeShape = b2EdgeShape()
        edgeShape.vertices = [(0.0, 0.0), (1.0, 0.0)]
        self.edgeBody: b2Body = self.world.CreateBody(bodyDef)
        edgeFixtureDef = b2FixtureDef()
        edgeFixtureDef.shape = edgeShape
        edgeFixtureDef.density = 2
        self.edgeBody.CreateFixture(edgeFixtureDef)