Files
EVEngine/src/modules/physics/WorldDebugDraw.cpp
T

130 lines
4.2 KiB
C++

#include "physics/World.h"
#include "graphics/Canvas.h"
#include "graphics/Graphics.h"
#include <Box2D/Box2D.h>
#include <algorithm>
#include <cmath>
namespace eve::physics {
namespace {
using eve::graphics::Color;
/**
* @brief Presentation-only Box2D debug draw sink.
*
* It is created for one draw call and never becomes part of World state. The
* solver therefore remains usable by server/headless builds without graphics.
*/
class DebugDraw final : public b2Draw {
public:
DebugDraw() { SetFlags(e_shapeBit | e_jointBit | e_aabbBit); }
void begin(graphics::Graphics *gfx, float meter) {
gfx_ = gfx;
meter_ = meter;
}
void end() { gfx_ = nullptr; }
void DrawPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) override {
drawPoly(vertices, vertexCount, color, false);
}
void DrawSolidPolygon(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color) override {
drawPoly(vertices, vertexCount, color, true);
}
void DrawCircle(const b2Vec2 &center, float32 radius, const b2Color &color) override {
drawCircle(center, radius, color, false);
}
void DrawSolidCircle(const b2Vec2 &center, float32 radius, const b2Vec2 & /*axis*/, const b2Color &color) override {
drawCircle(center, radius, color, true);
}
void DrawSegment(const b2Vec2 &p1, const b2Vec2 &p2, const b2Color &color) override {
if (!gfx_) return;
float x1 = p1.x * meter_;
float y1 = p1.y * meter_;
float x2 = p2.x * meter_;
float y2 = p2.y * meter_;
float minx = std::min(x1, x2);
float miny = std::min(y1, y2);
float w = std::max(1.f, std::fabs(x2 - x1));
float h = std::max(1.f, std::fabs(y2 - y1));
gfx_->drawSolidRect(minx, miny, w, h, Color(color.r, color.g, color.b, color.a * 0.8f));
}
void DrawTransform(const b2Transform &xf) override {
DrawSegment(xf.p, xf.p + 0.5f * xf.q.GetXAxis(), b2Color(1, 0, 0));
DrawSegment(xf.p, xf.p + 0.5f * xf.q.GetYAxis(), b2Color(0, 1, 0));
}
private:
void drawPoly(const b2Vec2 *vertices, int32 vertexCount, const b2Color &color, bool solid) {
if (!gfx_ || vertexCount < 2) return;
for (int32 i = 0; i < vertexCount; ++i) {
const b2Vec2 &a = vertices[i];
const b2Vec2 &b = vertices[(i + 1) % vertexCount];
DrawSegment(a, b, color);
}
if (solid && vertexCount >= 3) {
float minx = vertices[0].x;
float maxx = vertices[0].x;
float miny = vertices[0].y;
float maxy = vertices[0].y;
for (int32 i = 1; i < vertexCount; ++i) {
minx = std::min(minx, vertices[i].x);
maxx = std::max(maxx, vertices[i].x);
miny = std::min(miny, vertices[i].y);
maxy = std::max(maxy, vertices[i].y);
}
gfx_->drawSolidRect(minx * meter_, miny * meter_, (maxx - minx) * meter_, (maxy - miny) * meter_,
Color(color.r, color.g, color.b, color.a * 0.25f));
}
}
void drawCircle(const b2Vec2 &center, float32 radius, const b2Color &color, bool solid) {
if (!gfx_) return;
float px = (center.x - radius) * meter_;
float py = (center.y - radius) * meter_;
float d = radius * 2.f * meter_;
float a = solid ? color.a * 0.35f : color.a * 0.7f;
gfx_->drawSolidRect(px, py, d, d, Color(color.r, color.g, color.b, a));
}
graphics::Graphics *gfx_ = nullptr;
float meter_ = 30.f;
};
class DebugDrawBinding final {
public:
DebugDrawBinding(b2World *world, b2Draw *draw) : world_(world) { world_->SetDebugDraw(draw); }
~DebugDrawBinding() { world_->SetDebugDraw(nullptr); }
DebugDrawBinding(const DebugDrawBinding &) = delete;
DebugDrawBinding &operator=(const DebugDrawBinding &) = delete;
private:
b2World *world_;
};
} // namespace
void World::drawDebug(graphics::Graphics *gfx) {
b2World *world = raw();
if (!world || !isValid() || !gfx) return;
DebugDraw draw;
draw.begin(gfx, getMeter());
DebugDrawBinding binding(world, &draw);
world->DrawDebugData();
draw.end();
}
} // namespace eve::physics