Files
HelloVulkan/Engine/src/Debug/DebugDraw.cpp
T
atdunbg 36b1fc0566 新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
2026-10-08 17:02:27 +08:00

360 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// DebugDraw.cpp:通用调试图元服务实现。
//
// 结构:瞬时缓冲(duration=0,调用方每帧重画)+ 持久列表(duration>0,
// 按到期时间淘汰)→ Frame() 按 (拓扑,线宽,深度测试) 键聚合为连续顶点段
// → Renderer 上传 → EndFrame() 翻页。聚合器不缓存跨帧顶点,持久项每帧
// 重拷贝(调试工具量级,< 数千顶点,非热路径)。
//
#include <Engine/Debug/DebugDraw.h>
#include <Engine/Core/Log.h>
#include <algorithm>
#include <cmath>
#include <chrono>
namespace Engine::Debug {
namespace {
constexpr f32 kPi = 3.14159265358979f;
// 瞬时批次:Draw 调用直接追加(批次键匹配复用,保留容量)
struct TransientBatch {
f32 width;
bool depthTest;
std::vector<Vertex> verts;
};
// 持久项:快照顶点 + 到期时刻(steady_clock 秒)
struct Persistent {
f32 width;
bool depthTest;
std::vector<Vertex> verts;
f64 expiry;
};
bool g_Enabled = true;
bool g_Warned = false;
std::vector<TransientBatch> g_Transient;
std::vector<Persistent> g_Persistent;
DebugFrame g_Frame;
using Clock = std::chrono::steady_clock;
f64 NowSec()
{
return std::chrono::duration<f64>(Clock::now().time_since_epoch())
.count();
}
TransientBatch& BatchFor(f32 width, bool depthTest)
{
for (auto& b : g_Transient) {
if (b.depthTest == depthTest &&
std::abs(b.width - width) < 1e-3f) {
return b;
}
}
g_Transient.push_back(TransientBatch{width, depthTest, {}});
return g_Transient.back();
}
void LineImpl(const glm::vec3& a, const glm::vec3& b,
const glm::vec4& color, f32 width, f32 duration,
bool depthTest)
{
if (!g_Enabled) return;
if (duration > 0.0f) {
Persistent p;
p.width = width;
p.depthTest = depthTest;
p.expiry = NowSec() + static_cast<f64>(duration);
p.verts.push_back({a, color});
p.verts.push_back({b, color});
g_Persistent.push_back(std::move(p));
} else {
auto& batch = BatchFor(width, depthTest);
batch.verts.push_back({a, color});
batch.verts.push_back({b, color});
}
}
// 圆折线发射器:center + u·cosθ + v·sinθ(u/v 已含半径)
void CircleImpl(const glm::vec3& center, const glm::vec3& u,
const glm::vec3& v, const glm::vec4& color,
int segments, f32 width, f32 duration,
bool depthTest)
{
glm::vec3 prev = center + u;
for (int i = 1; i <= segments; ++i) {
const f32 t =
static_cast<f32>(i) / static_cast<f32>(segments) * 2.0f * kPi;
const glm::vec3 p = center + u * std::cos(t) + v * std::sin(t);
LineImpl(prev, p, color, width, duration, depthTest);
prev = p;
}
}
} // namespace
void SetEnabled(bool on)
{
g_Enabled = on;
if (!on) {
Clear();
}
}
bool IsEnabled() { return g_Enabled; }
void Clear()
{
g_Transient.clear();
g_Persistent.clear();
g_Frame.Clear();
}
void Line(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color,
f32 width, f32 duration, bool depthTest)
{
LineImpl(a, b, color, width, duration, depthTest);
}
void Ray(const glm::vec3& from, const glm::vec3& dirNorm, f32 length,
const glm::vec4& color, f32 width, f32 duration, bool depthTest)
{
LineImpl(from, from + dirNorm * length, color, width, duration,
depthTest);
}
void Arrow(const glm::vec3& from, const glm::vec3& to,
const glm::vec4& color, f32 headSize, f32 width, f32 duration,
bool depthTest)
{
const glm::vec3 d = to - from;
const f32 lenSq = glm::dot(d, d);
if (lenSq < 1e-12f) {
// 零长退化:画十字点标(而不是未定义方向的箭头)
Cross(from, headSize * 0.5f, color, width, duration, depthTest);
return;
}
LineImpl(from, to, color, width, duration, depthTest);
// 箭头四翼:轴周围 90° 均布两对 V(60° 张角)。单 V 在视线与
// 翼面共面时退化成一条线;四翼保证任意视角至少一对正对相机
const glm::vec3 dir = d * (1.0f / std::sqrt(lenSq));
const glm::vec3 up =
std::abs(dir.y) < 0.99f ? glm::vec3(0.0f, 1.0f, 0.0f)
: glm::vec3(1.0f, 0.0f, 0.0f);
const glm::vec3 side = glm::normalize(glm::cross(dir, up));
const glm::vec3 bit = glm::normalize(glm::cross(dir, side));
const glm::vec3 heads[4] = {
glm::normalize(dir + side * 0.6f),
glm::normalize(dir - side * 0.6f),
glm::normalize(dir + bit * 0.6f),
glm::normalize(dir - bit * 0.6f),
};
for (const glm::vec3& h : heads) {
LineImpl(to, to - h * headSize, color, width, duration, depthTest);
}
}
void Cross(const glm::vec3& pos, f32 size, const glm::vec4& color,
f32 width, f32 duration, bool depthTest)
{
const glm::vec3 s(size, size, size);
LineImpl(pos - glm::vec3(size, 0, 0), pos + glm::vec3(size, 0, 0),
color, width, duration, depthTest);
LineImpl(pos - glm::vec3(0, size, 0), pos + glm::vec3(0, size, 0),
color, width, duration, depthTest);
LineImpl(pos - glm::vec3(0, 0, size), pos + glm::vec3(0, 0, size),
color, width, duration, depthTest);
(void)s;
}
void WireSphere(const glm::vec3& center, f32 radius,
const glm::vec4& color, int segments, f32 width,
f32 duration, bool depthTest)
{
// 三大圆:XY / XZ / YZ 平面
CircleImpl(center, {radius, 0, 0}, {0, radius, 0}, color, segments,
width, duration, depthTest);
CircleImpl(center, {radius, 0, 0}, {0, 0, radius}, color, segments,
width, duration, depthTest);
CircleImpl(center, {0, radius, 0}, {0, 0, radius}, color, segments,
width, duration, depthTest);
}
void WireCircle(const glm::vec3& center, f32 radius,
const glm::vec3& normal, const glm::vec4& color,
int segments, f32 width, f32 duration, bool depthTest)
{
const f32 nSq = glm::dot(normal, normal);
if (nSq < 1e-12f) return;
const glm::vec3 n = normal * (1.0f / std::sqrt(nSq));
const glm::vec3 up = std::abs(n.y) < 0.99f
? glm::vec3(0.0f, 1.0f, 0.0f)
: glm::vec3(1.0f, 0.0f, 0.0f);
const glm::vec3 u = glm::normalize(glm::cross(n, up)) * radius;
const glm::vec3 v = glm::normalize(glm::cross(n, u / radius)) * radius;
CircleImpl(center, u, v, color, segments, width, duration, depthTest);
}
void WireBox(const glm::vec3& center, const glm::vec3& halfExtents,
const glm::vec4& color, const glm::quat& rot, f32 width,
f32 duration, bool depthTest)
{
glm::vec3 corner[8];
int ci = 0;
for (int i = 0; i < 8; ++i) {
const glm::vec3 local(
(i & 1) ? halfExtents.x : -halfExtents.x,
(i & 2) ? halfExtents.y : -halfExtents.y,
(i & 4) ? halfExtents.z : -halfExtents.z);
corner[ci++] = center + rot * local;
}
// 12 条边
static constexpr int kEdges[12][2] = {
{0, 1}, {2, 3}, {4, 5}, {6, 7}, // X 向
{0, 2}, {1, 3}, {4, 6}, {5, 7}, // Y 向
{0, 4}, {1, 5}, {2, 6}, {3, 7}, // Z 向
};
for (const auto& e : kEdges) {
LineImpl(corner[e[0]], corner[e[1]], color, width, duration,
depthTest);
}
}
void WireCapsule(const glm::vec3& center, f32 radius, f32 halfHeight,
const glm::vec4& color, const glm::quat& rot, f32 width,
f32 duration, bool depthTest)
{
// 轴向 local +Y:两端圆 + 4 侧线 + 两盖各 2 半圆(X-Y / Z-Y 平面)
auto W = [&](const glm::vec3& local) { return center + rot * local; };
auto CircleY = [&](f32 y) {
glm::vec3 prev;
for (int i = 0; i <= 16; ++i) {
const f32 t = static_cast<f32>(i) / 16.0f * 2.0f * kPi;
const glm::vec3 p = W({radius * std::cos(t), y,
radius * std::sin(t)});
if (i > 0) {
LineImpl(prev, p, color, width, duration, depthTest);
}
prev = p;
}
};
CircleY(halfHeight);
CircleY(-halfHeight);
LineImpl(W({radius, halfHeight, 0}), W({radius, -halfHeight, 0}),
color, width, duration, depthTest);
LineImpl(W({-radius, halfHeight, 0}), W({-radius, -halfHeight, 0}),
color, width, duration, depthTest);
LineImpl(W({0, halfHeight, radius}), W({0, -halfHeight, radius}),
color, width, duration, depthTest);
LineImpl(W({0, halfHeight, -radius}), W({0, -halfHeight, -radius}),
color, width, duration, depthTest);
auto CapArc = [&](f32 cap, f32 xz) {
// xz: 半圆所在竖直平面(1 = X-Y 面,0 = Z-Y 面)
glm::vec3 prev;
for (int i = 0; i <= 8; ++i) {
const f32 a = static_cast<f32>(i) / 8.0f * kPi;
const f32 radial = radius * std::cos(a);
const f32 axial = cap * (halfHeight + radius * std::sin(a));
const glm::vec3 p =
W({radial * xz, axial, radial * (1.0f - xz)});
if (i > 0) {
LineImpl(prev, p, color, width, duration, depthTest);
}
prev = p;
}
};
CapArc(1.0f, 1.0f);
CapArc(-1.0f, 1.0f);
CapArc(1.0f, 0.0f);
CapArc(-1.0f, 0.0f);
}
const DebugFrame& Frame()
{
g_Frame.Clear();
if (!g_Enabled) {
return g_Frame;
}
// ---- 按 (拓扑=线, 线宽, 深度) 聚合:瞬时 + 未过期持久项 ----
// groups = 聚合组暂存(键少量,线性扫)→ 展开为连续顶点段
std::vector<std::pair<Batch, std::vector<Vertex>>> groups;
auto findGroup = [&](f32 w, bool d) -> std::vector<Vertex>& {
for (auto& [b, vs] : groups) {
if (!b.tris && b.depthTest == d &&
std::abs(b.width - w) < 1e-3f) {
return vs;
}
}
groups.push_back({Batch{false, w, d, 0, 0}, {}});
return groups.back().second;
};
for (const auto& t : g_Transient) {
findGroup(t.width, t.depthTest)
.insert(findGroup(t.width, t.depthTest).end(),
t.verts.begin(), t.verts.end());
}
const f64 now = NowSec();
for (const auto& p : g_Persistent) {
if (p.expiry > now) {
findGroup(p.width, p.depthTest)
.insert(findGroup(p.width, p.depthTest).end(),
p.verts.begin(), p.verts.end());
}
}
// ---- 展开 + 预算截断 ----
u64 total = 0;
for (const auto& [b, vs] : groups) {
total += vs.size();
}
const u64 cap = kMaxVerts;
if (total > cap && !g_Warned) {
g_Warned = true;
ENGINE_WARN("DebugDraw: vertex budget exceeded ({} > {}), "
"clamped",
total, cap);
}
for (auto& [b, vs] : groups) {
if (g_Frame.verts.size() >= cap) break;
b.first = static_cast<u32>(g_Frame.verts.size());
const u64 room = cap - g_Frame.verts.size();
const u64 n = std::min<u64>(vs.size(), room);
g_Frame.verts.insert(g_Frame.verts.end(), vs.begin(),
vs.begin() + static_cast<i64>(n));
b.count = static_cast<u32>(n);
if (b.count > 0) {
g_Frame.batches.push_back(b);
}
}
return g_Frame;
}
void EndFrame()
{
g_Frame.Clear();
const f64 now = NowSec();
g_Persistent.erase(std::remove_if(g_Persistent.begin(),
g_Persistent.end(),
[now](const Persistent& p) {
return p.expiry <= now;
}),
g_Persistent.end());
for (auto& t : g_Transient) {
t.verts.clear(); // 保留批次与容量(稳态零重分配)
}
}
} // namespace Engine::Debug