新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线

通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
This commit is contained in:
2026-10-08 17:02:27 +08:00
parent ff955b9e63
commit 36b1fc0566
10 changed files with 903 additions and 44 deletions
+81 -16
View File
@@ -3,6 +3,7 @@
//
#include <Engine/Physics/PhysicsWorld.h>
#include <Engine/Debug/DebugDraw.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Components.h>
@@ -274,12 +275,14 @@ namespace Engine {
void PhysicsWorld::CollectDebugData() {
m_DebugDraw->BeginCollect();
// 1) Jolt 原生:collider 线框 + 速度箭头(软体 mDrawSoftBody* 后续加这里)
if ((m_DebugBits & (kPhyDbgColliders | kPhyDbgVelocity)) != 0) {
// 1) Jolt 原生:collider 线框(软体 mDrawSoftBody* 后续加这里)。
// 速度箭头不交 Jolt 画——其 BodyManager::DrawBodies 无零速保
// 护,静止刚体的零向量箭头退化成 +X 方向(本次修复的突兀箭
// 头)→ 统一在下方手画并加阈值
if ((m_DebugBits & kPhyDbgColliders) != 0) {
JPH::BodyManager::DrawSettings ds;
ds.mDrawShape = (m_DebugBits & kPhyDbgColliders) != 0;
ds.mDrawShape = true;
ds.mDrawShapeWireframe = true;
ds.mDrawVelocity = (m_DebugBits & kPhyDbgVelocity) != 0;
m_System->DrawBodies(ds, m_DebugDraw.get());
}
@@ -289,39 +292,86 @@ namespace Engine {
}
// 3) 手绘箭头(Jolt 无现成 API):重力 = 常量方向指示(长度固定),
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)
// 加速度 = 速度差分(方向/长度随值,静止噪声阈值截断)。
// 走引擎通用 DebugDraw 服务。收集按渲染帧率跑(Step 末尾
// 无步进帧也重画,每帧全量重建)→ duration 必须 0(当帧瞬
// 时):此前沿用 step 率时代的 2×步长续期,高帧率下每根箭
// 头叠 4-5 个滞后副本,物体一动副本脱锚拖尾——表现为断断
// 续续乱跳
auto &bi = m_System->GetBodyInterface();
const JPH::Vec3 gravity = m_System->GetGravity();
constexpr f32 kGravityLen = 1.0f; // 世界单位
constexpr f32 kAccelScale = 0.05f;
constexpr f32 kAccelMaxLen = 2.0f;
const JPH::Color kGravityColor(220, 60, 60); // 红:重力
const JPH::Color kAccelColor(255, 170, 40); // 橙:加速度
constexpr f32 kVelScale = 0.2f; // 速度箭头比例(单位/速度)
constexpr f32 kDbgDur = 0.0f; // 渲染帧率收集 → 瞬时图元
constexpr glm::vec4 kGravityColor = Debug::Rgb(0xDC3C3C); // 红:重力
constexpr glm::vec4 kAccelColor = Debug::Rgb(0xE0640A); // 深橙:加速度
constexpr glm::vec4 kVelColor = Debug::Rgb(0x5ADC5A); // 绿:速度
constexpr glm::vec4 kContactColor = Debug::Rgb(0xD9C33C); // 黄:接触反力
auto ToG = [](const JPH::RVec3Arg v) {
return glm::vec3{static_cast<f32>(v.GetX()),
static_cast<f32>(v.GetY()),
static_cast<f32>(v.GetZ())};
};
for (auto &[bits, rec]: m_Bodies) {
if (rec.type != static_cast<u8>(ECS::RigidBodyComponent::BodyType::Dynamic)) {
continue;
}
const JPH::RVec3 com = bi.GetCenterOfMassPosition(rec.id);
const glm::vec3 com = ToG(bi.GetCenterOfMassPosition(rec.id));
// 速度箭头(绿):零速阈值——Jolt 原生 DrawBodies 无零速保
// 护(静止零向量箭头退化成 +X),此处阈值手画
if ((m_DebugBits & kPhyDbgVelocity) != 0) {
const JPH::Vec3 v = bi.GetLinearVelocity(rec.id);
if (v.LengthSq() > 1e-4f) { // >0.01 m/s 才画
Debug::Arrow(com, com + ToG(JPH::RVec3(v * kVelScale)),
kVelColor,
/*headSize*/ 0.12f, /*width*/ 2.0f,
/*duration*/ kDbgDur, /*depthTest*/ false);
}
}
if ((m_DebugBits & kPhyDbgGravity) != 0) {
const JPH::Vec3 g = gravity * bi.GetGravityFactor(rec.id);
const f32 len = g.Length();
if (len > 1e-4f) {
const JPH::RVec3 tip = com + JPH::RVec3(g * (kGravityLen / len));
m_DebugDraw->DrawArrow(com, tip, kGravityColor, kGravityLen * 0.12f);
Debug::Arrow(
com,
com + ToG(JPH::RVec3(g * (kGravityLen / len))),
kGravityColor,
/*headSize*/ 0.12f, /*width*/ 2.0f,
/*duration*/ kDbgDur, /*depthTest*/ false);
}
}
if ((m_DebugBits & kPhyDbgAccel) != 0) {
// 接触合力(比力 F/m = a − g,从测量加速度反推):静置
// = +g 纯法向(与重力箭头等长反向 = 力平衡);滑动出现
// 水平分量(摩擦);自由落体 ≈ 0 不画。长度按重力箭头
// 同一比例归一 → 静置红下黄上视觉等长
const JPH::Vec3 gB = gravity * bi.GetGravityFactor(rec.id);
const glm::vec3 spec = rec.lastAccel -
glm::vec3(gB.GetX(), gB.GetY(), gB.GetZ());
const f32 smag = glm::length(spec);
if (smag > 0.2f && gravity.Length() > 1e-4f) {
const f32 slen = std::min(
smag * kGravityLen / gravity.Length(), kAccelMaxLen);
Debug::Arrow(com, com + (spec / smag) * slen,
kContactColor,
/*headSize*/ 0.12f, /*width*/ 2.0f,
/*duration*/ kDbgDur, /*depthTest*/ false);
}
const f32 mag = glm::length(rec.lastAccel);
if (mag > 0.05f) {
const glm::vec3 dir = rec.lastAccel / mag;
const f32 len = std::min(mag * kAccelScale, kAccelMaxLen);
const JPH::RVec3 tip =
com + JPH::RVec3(dir.x, dir.y, dir.z) * len;
m_DebugDraw->DrawArrow(
com, tip, kAccelColor, std::max(len * 0.15f, 0.05f));
Debug::Arrow(com, com + (rec.lastAccel / mag) * len,
kAccelColor,
/*headSize*/ 0.12f,
/*width*/ 2.0f,
/*duration*/ kDbgDur, /*depthTest*/ false);
}
}
}
@@ -334,6 +384,10 @@ namespace Engine {
}
}
// 角色运动类线条(速度/加速度)一律归用户脚本画(Unity
// CharacterController 同语义:引擎只画 collider,移动调试归
// 移动逻辑的拥有者);物理侧不保留任何角色箭头
// 释放上一帧缓存不再引用的通用几何 batch(DebugRenderer 内部池)
m_DebugDraw->NextFrame();
}
@@ -675,7 +729,12 @@ namespace Engine {
if (tr == nullptr) {
continue;
}
entry.cc->Update(dt, gravity, *m_TempAllocator);
// 每角色重力倍率(组件直读:Inspector 调完下个物理步即生效,
// 不触发重建——重力只是 Update 的入参,不是形状参数)
const auto *cc = reg.try_get<ECS::CharacterControllerComponent>(
static_cast<entt::entity>(bits));
const f32 gscale = cc != nullptr ? cc->gravityScale : 1.0f;
entry.cc->Update(dt, gravity * gscale, *m_TempAllocator);
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底(世界系)
// 写回走 world→local 换算(层级实体挂父下仍正确;标脏 →
// UpdateTransforms 重算 world)
@@ -1047,6 +1106,12 @@ namespace Engine {
: glm::vec3{0.0f};
}
glm::vec3 PhysicsWorld::GetCharacterPosition(const u32 entityBits) const {
const auto found = m_Characters.find(entityBits);
return found != m_Characters.end() ? found->second.cc->GetPosition()
: glm::vec3{0.0f};
}
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
void PhysicsWorld::Writeback() {