// // DebugDraw.cpp:通用调试图元服务实现。 // // 结构:瞬时缓冲(duration=0,调用方每帧重画)+ 持久列表(duration>0, // 按到期时间淘汰)→ Frame() 按 (拓扑,线宽,深度测试) 键聚合为连续顶点段 // → Renderer 上传 → EndFrame() 翻页。聚合器不缓存跨帧顶点,持久项每帧 // 重拷贝(调试工具量级,< 数千顶点,非热路径)。 // #include #include #include #include #include namespace Engine::Debug { namespace { constexpr f32 kPi = 3.14159265358979f; // 瞬时批次:Draw 调用直接追加(批次键匹配复用,保留容量) struct TransientBatch { f32 width; bool depthTest; std::vector verts; }; // 持久项:快照顶点 + 到期时刻(steady_clock 秒) struct Persistent { f32 width; bool depthTest; std::vector verts; f64 expiry; }; bool g_Enabled = true; bool g_Warned = false; std::vector g_Transient; std::vector g_Persistent; DebugFrame g_Frame; using Clock = std::chrono::steady_clock; f64 NowSec() { return std::chrono::duration(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(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(i) / static_cast(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(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(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>> groups; auto findGroup = [&](f32 w, bool d) -> std::vector& { 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(g_Frame.verts.size()); const u64 room = cap - g_Frame.verts.size(); const u64 n = std::min(vs.size(), room); g_Frame.verts.insert(g_Frame.verts.end(), vs.begin(), vs.begin() + static_cast(n)); b.count = static_cast(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