通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合; 物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学 线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
360 lines
13 KiB
C++
360 lines
13 KiB
C++
//
|
||
// 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
|