Editor: group Details parameters and reorganize Stats panel

Details: multi-parameter components (RigidBody/Joint/CharacterController/
Animation) now use SeparatorText category headers in UE Details style;
all parameter labels normalized to Pascal Case.

Stats: rearranged into View / Scene / Rendering / Physics / Performance
collapsing groups. Clear Color moved to Scene, Debug View to Rendering,
GPU Timings + Frame merged into Performance; bare top-level widgets and
the now-obsolete Environment group removed.
This commit is contained in:
2026-10-05 01:20:00 +08:00
parent 432089747f
commit 5ca5ac2a1e
2 changed files with 141 additions and 123 deletions
+87 -63
View File
@@ -263,7 +263,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
} }
} }
glm::vec3 pos = t->GetPosition(); glm::vec3 pos = t->GetPosition();
if (DragVec3Row("position", pos, 0.05f)) if (DragVec3Row("Position", pos, 0.05f))
{ {
t->SetPosition(pos); t->SetPosition(pos);
} }
@@ -315,7 +315,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
glm::vec3 rot = s_RotMirror; glm::vec3 rot = s_RotMirror;
// "%.1f" = UE Details 默认显示精度。mirror 值直显(无 wrap、 // "%.1f" = UE Details 默认显示精度。mirror 值直显(无 wrap、
// 无限累计);Ctrl+Click 手输全精度写入 // 无限累计);Ctrl+Click 手输全精度写入
if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f, if (DragVec3Row("Rotation", rot, 0.5f, -3.4e38f, 3.4e38f,
"%.1f", &editingRot)) "%.1f", &editingRot))
{ {
t->SetRotation(rot); t->SetRotation(rot);
@@ -323,7 +323,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
} }
s_RotEditingLast = editingRot; s_RotEditingLast = editingRot;
glm::vec3 scl = t->GetScale(); glm::vec3 scl = t->GetScale();
if (DragVec3Row("scale", scl, 0.02f, 0.001f, 100.0f)) if (DragVec3Row("Scale", scl, 0.02f, 0.001f, 100.0f))
{ {
t->SetScale(scl); t->SetScale(scl);
} }
@@ -426,20 +426,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::BeginDisabled(true); ImGui::BeginDisabled(true);
} }
int type = static_cast<int>(light->type); int type = static_cast<int>(light->type);
if (ImGui::Combo("type", &type, "Directional\0Point\0")) if (ImGui::Combo("Type", &type, "Directional\0Point\0"))
{ {
light->type = static_cast<Engine::ECS::LightComponent::Type>(type); light->type = static_cast<Engine::ECS::LightComponent::Type>(type);
} }
ImGui::ColorEdit3("color", &light->color.x); ImGui::ColorEdit3("Color", &light->color.x);
ImGui::DragFloat("intensity", &light->color.a, 0.05f, 0.0f, 100.0f); ImGui::DragFloat("Intensity", &light->color.a, 0.05f, 0.0f, 100.0f);
if (light->type == Engine::ECS::LightComponent::Type::Directional) if (light->type == Engine::ECS::LightComponent::Type::Directional)
{ {
ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f); ImGui::SliderFloat3("Direction", &light->direction.x, -1.0f, 1.0f);
ImGui::Checkbox("castShadow", &light->castShadow); ImGui::Checkbox("Cast Shadows", &light->castShadow);
} }
else else
{ {
ImGui::DragFloat("radius", &light->radius, 0.1f, 0.0f, 100.0f); ImGui::DragFloat("Radius", &light->radius, 0.1f, 0.0f, 100.0f);
ImGui::TextDisabled("(position = entity Transform)"); ImGui::TextDisabled("(position = entity Transform)");
} }
if (!light->active) if (!light->active)
@@ -513,7 +513,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{ {
ImGui::BeginDisabled(true); ImGui::BeginDisabled(true);
} }
ImGui::ColorEdit4("ambient", &sl->ambientColor.x); ImGui::ColorEdit4("Ambient", &sl->ambientColor.x);
if (!sl->active) if (!sl->active)
{ {
ImGui::EndDisabled(); ImGui::EndDisabled();
@@ -535,7 +535,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{ {
ImGui::BeginDisabled(true); ImGui::BeginDisabled(true);
} }
if (ImGui::Checkbox("primary", &cam->primary) && cam->primary) if (ImGui::Checkbox("Primary", &cam->primary) && cam->primary)
{ {
// primary 互斥:启用本相机时清掉其余实体的 primary // primary 互斥:启用本相机时清掉其余实体的 primary
//(引擎取第一个 primary 作为游戏形态渲染视角) //(引擎取第一个 primary 作为游戏形态渲染视角)
@@ -590,42 +590,46 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
"! conflicts with Character Controller " "! conflicts with Character Controller "
"(disable or remove one)"); "(disable or remove one)");
} }
// ---- Body:类型与碰撞体选择(变更 → PhysicsWorld 重建 body)----
ImGui::SeparatorText("Body");
int type = static_cast<int>(rb->type); int type = static_cast<int>(rb->type);
if (ImGui::Combo("type", &type, "Static\0Dynamic\0Kinematic\0")) if (ImGui::Combo("Type", &type, "Static\0Dynamic\0Kinematic\0"))
{ {
rb->type = static_cast<Engine::ECS::RigidBodyComponent::BodyType>(type); rb->type = static_cast<Engine::ECS::RigidBodyComponent::BodyType>(type);
} }
int collider = static_cast<int>(rb->collider); int collider = static_cast<int>(rb->collider);
if (ImGui::Combo("collider", &collider, if (ImGui::Combo("Collider", &collider,
"Box\0Sphere\0Mesh\0Capsule\0Cylinder\0Compound\0")) "Box\0Sphere\0Mesh\0Capsule\0Cylinder\0Compound\0"))
{ {
rb->collider = static_cast<Engine::ECS::RigidBodyComponent::ColliderType>(collider); rb->collider = static_cast<Engine::ECS::RigidBodyComponent::ColliderType>(collider);
} }
// ---- Shape:碰撞体尺寸(随类型切换;× Transform 缩放烘焙)----
ImGui::SeparatorText("Shape");
if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Box) if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Box)
{ {
ImGui::DragFloat3("halfExtents", &rb->boxHalfExtents.x, 0.05f, 0.01f, 500.0f); ImGui::DragFloat3("Half Extents", &rb->boxHalfExtents.x, 0.05f, 0.01f, 500.0f);
} }
else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Sphere) else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Sphere)
{ {
ImGui::DragFloat("radius", &rb->sphereRadius, 0.05f, 0.01f, 500.0f); ImGui::DragFloat("Radius", &rb->sphereRadius, 0.05f, 0.01f, 500.0f);
} }
else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Capsule) else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Capsule)
{ {
// 半高 = 圆柱段半高(不含半球端盖),总高 = 2*(halfHeight + radius) // 半高 = 圆柱段半高(不含半球端盖),总高 = 2*(halfHeight + radius)
ImGui::DragFloat("radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f); ImGui::DragFloat("Radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("halfHeight", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f); ImGui::DragFloat("Half Height", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f);
} }
else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Cylinder) else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Cylinder)
{ {
ImGui::DragFloat("radius", &rb->cylinderRadius, 0.05f, 0.01f, 500.0f); ImGui::DragFloat("Radius", &rb->cylinderRadius, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("halfHeight", &rb->cylinderHalfHeight, 0.05f, 0.01f, 500.0f); ImGui::DragFloat("Half Height", &rb->cylinderHalfHeight, 0.05f, 0.01f, 500.0f);
} }
else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Compound) else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Compound)
{ {
// 子形状列表:尺寸/位置变更 → PhysicsWorld 重建 body(既有模式) // 子形状列表:尺寸/位置变更 → PhysicsWorld 重建 body(既有模式)
ImGui::Text("children: %d", static_cast<int>(rb->compound.size())); ImGui::Text("Children: %d", static_cast<int>(rb->compound.size()));
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("+ add")) if (ImGui::Button("+ Add"))
{ {
rb->compound.emplace_back(); rb->compound.emplace_back();
} }
@@ -636,24 +640,24 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::PushID(i); ImGui::PushID(i);
ImGui::Separator(); ImGui::Separator();
int sh = static_cast<int>(c.shape); int sh = static_cast<int>(c.shape);
if (ImGui::Combo("shape", &sh, "Box\0Sphere\0Capsule\0")) if (ImGui::Combo("Shape", &sh, "Box\0Sphere\0Capsule\0"))
{ {
c.shape = static_cast<Engine::ECS::RigidBodyComponent::CompoundChild::Shape>(sh); c.shape = static_cast<Engine::ECS::RigidBodyComponent::CompoundChild::Shape>(sh);
} }
if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Box) if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Box)
{ {
ImGui::DragFloat3("halfExtents", &c.boxHalfExtents.x, 0.05f, 0.01f, 500.0f); ImGui::DragFloat3("Half Extents", &c.boxHalfExtents.x, 0.05f, 0.01f, 500.0f);
} }
else else
{ {
ImGui::DragFloat("radius", &c.radius, 0.05f, 0.01f, 500.0f); ImGui::DragFloat("Radius", &c.radius, 0.05f, 0.01f, 500.0f);
if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Capsule) if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Capsule)
{ {
ImGui::DragFloat("halfHeight", &c.capsuleHalfHeight, 0.05f, 0.0f, 500.0f); ImGui::DragFloat("Half Height", &c.capsuleHalfHeight, 0.05f, 0.0f, 500.0f);
} }
} }
ImGui::DragFloat3("offset", &c.position.x, 0.05f); ImGui::DragFloat3("Offset", &c.position.x, 0.05f);
if (ImGui::Button("remove")) if (ImGui::Button("Remove"))
{ {
remove = i; remove = i;
} }
@@ -670,20 +674,22 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
// Dynamic=凸包近似),无尺寸可调 // Dynamic=凸包近似),无尺寸可调
ImGui::TextDisabled("follows MeshRenderer geometry"); ImGui::TextDisabled("follows MeshRenderer geometry");
} }
ImGui::DragFloat("mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f"); // ---- Simulation:动态体物理参数 ----
ImGui::SliderFloat("gravityScale", &rb->gravityScale, -1.0f, 5.0f); ImGui::SeparatorText("Simulation");
ImGui::SliderFloat("friction", &rb->friction, 0.0f, 2.0f); ImGui::DragFloat("Mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f");
ImGui::SliderFloat("restitution", &rb->restitution, 0.0f, 1.0f); ImGui::SliderFloat("Gravity Scale", &rb->gravityScale, -1.0f, 5.0f);
ImGui::Checkbox("isTrigger", &rb->isTrigger); ImGui::SliderFloat("Friction", &rb->friction, 0.0f, 2.0f);
ImGui::SliderFloat("Restitution", &rb->restitution, 0.0f, 1.0f);
ImGui::Checkbox("Is Trigger", &rb->isTrigger);
// ---- Debug:遥测与驱动调试(冲量瞬态不序列化)----
if (rb->bodyValid && if (rb->bodyValid &&
rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic) rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic)
{ {
ImGui::SeparatorText("Debug");
ImGui::Text("vel: %.2f, %.2f, %.2f m/s", ImGui::Text("vel: %.2f, %.2f, %.2f m/s",
rb->velocity.x, rb->velocity.y, rb->velocity.z); rb->velocity.x, rb->velocity.y, rb->velocity.z);
// 驱动调试(冲量瞬态不序列化):直接推一把看响应
static glm::vec3 s_debugImpulse{0.0f, 5.0f, 0.0f}; static glm::vec3 s_debugImpulse{0.0f, 5.0f, 0.0f};
ImGui::DragFloat3("impulse", &s_debugImpulse.x, 0.1f); ImGui::DragFloat3("Impulse", &s_debugImpulse.x, 0.1f);
if (ImGui::Button("Apply Impulse")) if (ImGui::Button("Apply Impulse"))
{ {
ctx.app->GetPhysics().AddImpulse( ctx.app->GetPhysics().AddImpulse(
@@ -718,8 +724,10 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f),
"! requires a RigidBody on this entity"); "! requires a RigidBody on this entity");
} }
// ---- Constraint:类型与目标体 ----
ImGui::SeparatorText("Constraint");
int type = static_cast<int>(jc->type); int type = static_cast<int>(jc->type);
if (ImGui::Combo("type", &type, "Fixed\0Hinge\0Ball\0")) if (ImGui::Combo("Type", &type, "Fixed\0Hinge\0Ball\0"))
{ {
jc->type = static_cast<Engine::ECS::JointComponent::Type>(type); jc->type = static_cast<Engine::ECS::JointComponent::Type>(type);
} }
@@ -745,21 +753,25 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
items.push_back('\0'); items.push_back('\0');
}); });
items.push_back('\0'); // ImGui Combo 双 \0 结尾 items.push_back('\0'); // ImGui Combo 双 \0 结尾
if (ImGui::Combo("target", &current, items.c_str()) && current >= 0) if (ImGui::Combo("Target", &current, items.c_str()) && current >= 0)
{ {
jc->target = uuids[static_cast<size_t>(current)]; jc->target = uuids[static_cast<size_t>(current)];
} }
ImGui::DragFloat3("anchor A", &jc->anchorA.x, 0.05f); // ---- Anchors:各体局部系锚点 ----
ImGui::DragFloat3("anchor B", &jc->anchorB.x, 0.05f); ImGui::SeparatorText("Anchors");
ImGui::DragFloat3("Anchor A", &jc->anchorA.x, 0.05f);
ImGui::DragFloat3("Anchor B", &jc->anchorB.x, 0.05f);
// ---- Hinge:转轴与角度限位(度)----
if (jc->type == Engine::ECS::JointComponent::Type::Hinge) if (jc->type == Engine::ECS::JointComponent::Type::Hinge)
{ {
ImGui::DragFloat3("hinge axis", &jc->hingeAxis.x, 0.05f); ImGui::SeparatorText("Hinge");
ImGui::Checkbox("enable limit", &jc->enableLimit); ImGui::DragFloat3("Hinge Axis", &jc->hingeAxis.x, 0.05f);
ImGui::Checkbox("Enable Limit", &jc->enableLimit);
if (jc->enableLimit) if (jc->enableLimit)
{ {
ImGui::DragFloat("min deg", &jc->limitMin, 1.0f, -180.0f, 0.0f); ImGui::DragFloat("Limit Min", &jc->limitMin, 1.0f, -180.0f, 0.0f);
ImGui::DragFloat("max deg", &jc->limitMax, 1.0f, 0.0f, 180.0f); ImGui::DragFloat("Limit Max", &jc->limitMax, 1.0f, 0.0f, 180.0f);
} }
} }
if (!jc->active) if (!jc->active)
@@ -790,16 +802,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
"! conflicts with RigidBody " "! conflicts with RigidBody "
"(disable or remove one)"); "(disable or remove one)");
} }
// 实体原点 = 脚底(胶囊底端),总高 = 2*(halfHeight + radius) // ---- Body:胶囊体(实体原点 = 脚底,总高 = 2*(halfHeight + radius))----
ImGui::DragFloat("radius", &cc->radius, 0.02f, 0.05f, 5.0f); ImGui::SeparatorText("Body");
ImGui::DragFloat("halfHeight", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f); ImGui::DragFloat("Radius", &cc->radius, 0.02f, 0.05f, 5.0f);
ImGui::DragFloat("mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f"); ImGui::DragFloat("Half Height", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f);
ImGui::DragFloat("max slope deg", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f); ImGui::DragFloat("Mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f");
ImGui::DragFloat("step height", &cc->stepHeight, 0.02f, 0.0f, 2.0f); // ---- Movement:扫掠运动学参数 ----
ImGui::SeparatorText("Movement");
ImGui::DragFloat("Max Slope (deg)", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f);
ImGui::DragFloat("Step Height", &cc->stepHeight, 0.02f, 0.0f, 2.0f);
// 遥测(只读):运行时状态在 PhysicsWorld 的 CharacterController // ---- Debug:遥测(只读,运行时状态在 PhysicsWorld)----
if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled()) if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled())
{ {
ImGui::SeparatorText("Debug");
const uint32_t bits = const uint32_t bits =
static_cast<uint32_t>(entt::to_integral(selected)); static_cast<uint32_t>(entt::to_integral(selected));
ImGui::Text("grounded: %s", ImGui::Text("grounded: %s",
@@ -980,10 +996,16 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f),
"! requires a MeshRenderer component"); "! requires a MeshRenderer component");
} }
else if (model != nullptr) else if (model == nullptr)
{ {
// 动画资产槽:列全部模型容器文件(无法静态区分"模型 ImGui::TextDisabled("(no model assigned)");
// fbx"与"纯动画 fbx",一并列出,无 clip 的会警告跳过) }
else
{
// ---- Clips:动画资产槽(列全部模型容器文件——无法静态
// 区分"模型 fbx"与"纯动画 fbx",一并列出,无 clip 的会
// 警告跳过)----
ImGui::SeparatorText("Clips");
const auto& files = AssetModelFiles(); const auto& files = AssetModelFiles();
std::vector<const char*> opts; std::vector<const char*> opts;
opts.push_back("<attach animation file...>"); opts.push_back("<attach animation file...>");
@@ -1016,9 +1038,15 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
.string() .string()
.c_str()); .c_str());
} }
if (!model->HasAnimations())
{
ImGui::TextDisabled("(no animation clips on model)");
}
} }
if (model != nullptr && model->HasAnimations()) if (model != nullptr && model->HasAnimations())
{ {
// ---- Playback:播放控制(切换 clip 触发 crossfade)----
ImGui::SeparatorText("Playback");
const auto& clips = model->GetClips(); const auto& clips = model->GetClips();
std::vector<const char*> clipNames; std::vector<const char*> clipNames;
clipNames.reserve(clips.size()); clipNames.reserve(clips.size());
@@ -1027,16 +1055,16 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
clipNames.push_back(c.name.c_str()); clipNames.push_back(c.name.c_str());
} }
int cur = anim->clipIndex; int cur = anim->clipIndex;
if (ImGui::Combo("clip", &cur, clipNames.data(), if (ImGui::Combo("Clip", &cur, clipNames.data(),
static_cast<int>(clipNames.size()))) static_cast<int>(clipNames.size())))
{ {
anim->Play(cur); // 触发 crossfade(fade 秒) anim->Play(cur); // 触发 crossfade(fade 秒)
} }
ImGui::Checkbox("playing", &anim->playing); ImGui::Checkbox("Playing", &anim->playing);
ImGui::SameLine(); ImGui::SameLine();
ImGui::Checkbox("loop", &anim->loop); ImGui::Checkbox("Loop", &anim->loop);
ImGui::SliderFloat("speed", &anim->speed, -2.0f, 2.0f); ImGui::SliderFloat("Speed", &anim->speed, -2.0f, 2.0f);
ImGui::SliderFloat("fade", &anim->fadeDuration, 0.0f, 1.0f); ImGui::SliderFloat("Fade", &anim->fadeDuration, 0.0f, 1.0f);
if (anim->clipIndex >= 0 && if (anim->clipIndex >= 0 &&
anim->clipIndex < static_cast<int>(clips.size())) anim->clipIndex < static_cast<int>(clips.size()))
{ {
@@ -1046,7 +1074,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
// 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的 // 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的
// 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼 // 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼
// 命名不同源被跳过) // 命名不同源被跳过)
if (ImGui::TreeNode("clip data")) if (ImGui::TreeNode("Clip Data"))
{ {
ImGui::Columns(3, nullptr, false); ImGui::Columns(3, nullptr, false);
ImGui::SetColumnWidth(0, 150.0f); ImGui::SetColumnWidth(0, 150.0f);
@@ -1083,10 +1111,6 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::TreePop(); ImGui::TreePop();
} }
} }
else
{
ImGui::TextDisabled("(no animation clips on model)");
}
if (!anim->active) if (!anim->active)
{ {
ImGui::EndDisabled(); ImGui::EndDisabled();
+54 -60
View File
@@ -1,5 +1,8 @@
// //
// StatsPanel.cpp:遥测与全局设置实现(原 main.cpp Scene Debug 搬迁)。 // StatsPanel.cpp:遥测与全局设置实现(原 main.cpp Scene Debug 搬迁)。
// 面板按 View / Scene / Rendering / Physics / Performance 分组——
// 环境贴图/环境光已实体化(Skybox/SkyLight 组件,Details 配置),
// 此处只留场景级 ClearColor。
// //
#include "StatsPanel.h" #include "StatsPanel.h"
@@ -19,12 +22,23 @@ void StatsPanel::Draw(const EditorContext& ctx)
auto& renderer = *ctx.renderer; auto& renderer = *ctx.renderer;
auto& app = *ctx.app; auto& app = *ctx.app;
// ---- Camera:视图相机位姿(只读遥测;跟随渲染源分派)---- // ---- View:看哪里(渲染目标)+ 相机位姿(跟随渲染源分派)----
if (ImGui::CollapsingHeader("View", ImGuiTreeNodeFlags_DefaultOpen))
{ {
if (ctx.renderer->GetDisplayMode() == kDisplayViewport && ctx.editorCamera) static const char* displayTarget[] = {
"Fullscreen",
"Viewport",
};
int mode = renderer.GetDisplayMode();
if (ImGui::Combo("Display Target", &mode, displayTarget,
IM_ARRAYSIZE(displayTarget)))
{
renderer.SetDisplayMode(mode);
}
if (renderer.GetDisplayMode() == kDisplayViewport && ctx.editorCamera)
{ {
const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition(); const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition();
ImGui::Text("Editor camera pos"); ImGui::Text("Editor Camera Pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z); ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
} }
else else
@@ -34,7 +48,7 @@ void StatsPanel::Draw(const EditorContext& ctx)
{ {
// world 位姿(相机实体可能挂父层级下) // world 位姿(相机实体可能挂父层级下)
const glm::vec3& pos = camT->GetWorldPosition(); const glm::vec3& pos = camT->GetWorldPosition();
ImGui::Text("Game camera pos"); ImGui::Text("Game Camera Pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z); ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
} }
else else
@@ -42,35 +56,26 @@ void StatsPanel::Draw(const EditorContext& ctx)
ImGui::TextDisabled("(no primary camera)"); ImGui::TextDisabled("(no primary camera)");
} }
} }
ImGui::Separator();
} }
ImGui::Separator(); // ---- Scene:模拟控制 + 场景级设置 ----
static const char* displayTarget[] = { if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
"Fullscreen",
"Viewport",
};
int mode = renderer.GetDisplayMode();
if (ImGui::Combo("##DisplayTarget", &mode, displayTarget, IM_ARRAYSIZE(displayTarget))) {
renderer.SetDisplayMode(mode);
}
ImGui::Separator();
// ---- Scene:暂停模拟(引擎 Time 门面:动画/物理统一冻结)----
{ {
// 暂停模拟(引擎 Time 门面:动画/物理统一冻结)
bool paused = Time::TimeScale() == 0.0f; bool paused = Time::TimeScale() == 0.0f;
if (ImGui::Checkbox("Pause Simulation", &paused)) if (ImGui::Checkbox("Pause Simulation", &paused))
{ {
Time::SetTimeScale(paused ? 0.0f : 1.0f); Time::SetTimeScale(paused ? 0.0f : 1.0f);
} }
ImGui::ColorEdit4("Clear Color",
glm::value_ptr(ctx.scene->GetSettings().ClearColor));
} }
// ---- Rendering:渲染管线参数与开关 ---- // ---- Rendering:渲染管线参数与开关 ----
if (ImGui::CollapsingHeader("Rendering", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Rendering", ImGuiTreeNodeFlags_DefaultOpen))
{ {
f32 exposure = renderer.GetExposure(); f32 exposure = renderer.GetExposure();
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) if (ImGui::SliderFloat("Exposure", &exposure, 0.1f, 4.0f))
{ {
renderer.SetExposure(exposure); renderer.SetExposure(exposure);
} }
@@ -89,12 +94,12 @@ void StatsPanel::Draw(const EditorContext& ctx)
if (bloom) if (bloom)
{ {
f32 threshold = renderer.GetBloomThreshold(); f32 threshold = renderer.GetBloomThreshold();
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) if (ImGui::SliderFloat("Threshold", &threshold, 0.0f, 2.0f))
{ {
renderer.SetBloomThreshold(threshold); renderer.SetBloomThreshold(threshold);
} }
f32 strength = renderer.GetBloomStrength(); f32 strength = renderer.GetBloomStrength();
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) if (ImGui::SliderFloat("Strength", &strength, 0.0f, 2.0f))
{ {
renderer.SetBloomStrength(strength); renderer.SetBloomStrength(strength);
} }
@@ -118,14 +123,6 @@ void StatsPanel::Draw(const EditorContext& ctx)
{ {
renderer.SetLodEnabled(lod); // 关 = 恒绘原网格 renderer.SetLodEnabled(lod); // 关 = 恒绘原网格
} }
}
// ---- Environment:清屏色 + 调试视图(决定"看什么")。
// 环境贴图/环境光已迁为实体组件(Skybox/SkyLight),在 Details 配 ----
if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::ColorEdit4("ClearColor",
glm::value_ptr(ctx.scene->GetSettings().ClearColor));
// 调试视图:单看某个 pass 的中间输出(类 UE pass debug) // 调试视图:单看某个 pass 的中间输出(类 UE pass debug)
const auto& viewNames = renderer.GetDebugViewNames(); const auto& viewNames = renderer.GetDebugViewNames();
@@ -137,13 +134,13 @@ void StatsPanel::Draw(const EditorContext& ctx)
} }
} }
// ---- Physics Debug:物理调试可视化(collider/约束/重力/速度/加速度)---- // ---- Physics:调试可视化(collider/约束/重力/速度/加速度)----
if (ImGui::CollapsingHeader("Physics Debug")) if (ImGui::CollapsingHeader("Physics"))
{ {
auto& physics = app.GetPhysics(); auto& physics = app.GetPhysics();
u32 bits = physics.GetDebugDrawBits(); u32 bits = physics.GetDebugDrawBits();
bool master = bits != 0; bool master = bits != 0;
if (ImGui::Checkbox("Enable##PhysDebug", &master)) if (ImGui::Checkbox("Debug Draw##Phys", &master))
{ {
bits = master ? kPhyDbgDefault : 0; bits = master ? kPhyDbgDefault : 0;
} }
@@ -169,9 +166,32 @@ void StatsPanel::Draw(const EditorContext& ctx)
ImGui::Text("contacts (frame): %zu", physics.GetEvents().size()); ImGui::Text("contacts (frame): %zu", physics.GetEvents().size());
} }
// ---- GPU Timings:只读遥测(默认折叠,不占面板高度)---- // ---- Performance:帧率控制 + GPU 遥测(默认折叠,不占面板高度)----
if (ImGui::CollapsingHeader("GPU Timings")) if (ImGui::CollapsingHeader("Performance"))
{ {
bool vsync = app.GetVSync();
if (ImGui::Checkbox("VSync", &vsync))
{
app.SetVSync(vsync);
if (!vsync)
{
const f32 target = app.GetTargetFps();
app.SetTargetFps(target);
}
}
if (!vsync)
{
f32 target = app.GetTargetFps();
if (ImGui::SliderFloat("Target FPS", &target, 24.0f, 360.0f, "%.0f"))
{
app.SetTargetFps(target);
}
}
ImGui::Text("fps: %.1f frame: %llu",
1.0f / Time::Current().UnscaledDt(),
static_cast<unsigned long long>(app.GetFrameCount()));
ImGui::Separator();
ImGui::Text("instances: %u main / %u shadow", ImGui::Text("instances: %u main / %u shadow",
renderer.GetMainInstanceCount(), renderer.GetMainInstanceCount(),
renderer.GetShadowInstanceCount()); renderer.GetShadowInstanceCount());
@@ -188,31 +208,5 @@ void StatsPanel::Draw(const EditorContext& ctx)
} }
} }
// ---- Frame:帧率控制与显示 ----
if (ImGui::CollapsingHeader("Frame", ImGuiTreeNodeFlags_DefaultOpen))
{
bool vsync = app.GetVSync();
if (ImGui::Checkbox("VSync", &vsync))
{
app.SetVSync(vsync);
if (!vsync)
{
const f32 target = app.GetTargetFps();
app.SetTargetFps(target);
}
}
if (!vsync)
{
f32 target = app.GetTargetFps();
if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f"))
{
app.SetTargetFps(target);
}
}
ImGui::Text("fps: %.1f frame: %llu",
1.0f / Time::Current().UnscaledDt(),
static_cast<unsigned long long>(app.GetFrameCount()));
}
ImGui::End(); ImGui::End();
} }