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();
if (DragVec3Row("position", pos, 0.05f))
if (DragVec3Row("Position", pos, 0.05f))
{
t->SetPosition(pos);
}
@@ -315,7 +315,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
glm::vec3 rot = s_RotMirror;
// "%.1f" = UE Details 默认显示精度。mirror 值直显(无 wrap、
// 无限累计);Ctrl+Click 手输全精度写入
if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f,
if (DragVec3Row("Rotation", rot, 0.5f, -3.4e38f, 3.4e38f,
"%.1f", &editingRot))
{
t->SetRotation(rot);
@@ -323,7 +323,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
}
s_RotEditingLast = editingRot;
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);
}
@@ -426,20 +426,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::BeginDisabled(true);
}
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);
}
ImGui::ColorEdit3("color", &light->color.x);
ImGui::DragFloat("intensity", &light->color.a, 0.05f, 0.0f, 100.0f);
ImGui::ColorEdit3("Color", &light->color.x);
ImGui::DragFloat("Intensity", &light->color.a, 0.05f, 0.0f, 100.0f);
if (light->type == Engine::ECS::LightComponent::Type::Directional)
{
ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f);
ImGui::Checkbox("castShadow", &light->castShadow);
ImGui::SliderFloat3("Direction", &light->direction.x, -1.0f, 1.0f);
ImGui::Checkbox("Cast Shadows", &light->castShadow);
}
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)");
}
if (!light->active)
@@ -513,7 +513,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{
ImGui::BeginDisabled(true);
}
ImGui::ColorEdit4("ambient", &sl->ambientColor.x);
ImGui::ColorEdit4("Ambient", &sl->ambientColor.x);
if (!sl->active)
{
ImGui::EndDisabled();
@@ -535,7 +535,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{
ImGui::BeginDisabled(true);
}
if (ImGui::Checkbox("primary", &cam->primary) && cam->primary)
if (ImGui::Checkbox("Primary", &cam->primary) && cam->primary)
{
// primary 互斥:启用本相机时清掉其余实体的 primary
//(引擎取第一个 primary 作为游戏形态渲染视角)
@@ -590,42 +590,46 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
"! conflicts with Character Controller "
"(disable or remove one)");
}
// ---- Body:类型与碰撞体选择(变更 → PhysicsWorld 重建 body)----
ImGui::SeparatorText("Body");
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);
}
int collider = static_cast<int>(rb->collider);
if (ImGui::Combo("collider", &collider,
if (ImGui::Combo("Collider", &collider,
"Box\0Sphere\0Mesh\0Capsule\0Cylinder\0Compound\0"))
{
rb->collider = static_cast<Engine::ECS::RigidBodyComponent::ColliderType>(collider);
}
// ---- Shape:碰撞体尺寸(随类型切换;× Transform 缩放烘焙)----
ImGui::SeparatorText("Shape");
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)
{
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)
{
// 半高 = 圆柱段半高(不含半球端盖),总高 = 2*(halfHeight + radius)
ImGui::DragFloat("radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("halfHeight", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f);
ImGui::DragFloat("Radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("Half Height", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f);
}
else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Cylinder)
{
ImGui::DragFloat("radius", &rb->cylinderRadius, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("halfHeight", &rb->cylinderHalfHeight, 0.05f, 0.01f, 500.0f);
ImGui::DragFloat("Radius", &rb->cylinderRadius, 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)
{
// 子形状列表:尺寸/位置变更 → PhysicsWorld 重建 body(既有模式)
ImGui::Text("children: %d", static_cast<int>(rb->compound.size()));
ImGui::Text("Children: %d", static_cast<int>(rb->compound.size()));
ImGui::SameLine();
if (ImGui::Button("+ add"))
if (ImGui::Button("+ Add"))
{
rb->compound.emplace_back();
}
@@ -636,24 +640,24 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::PushID(i);
ImGui::Separator();
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);
}
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
{
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)
{
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);
if (ImGui::Button("remove"))
ImGui::DragFloat3("Offset", &c.position.x, 0.05f);
if (ImGui::Button("Remove"))
{
remove = i;
}
@@ -670,20 +674,22 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
// Dynamic=凸包近似),无尺寸可调
ImGui::TextDisabled("follows MeshRenderer geometry");
}
ImGui::DragFloat("mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f");
ImGui::SliderFloat("gravityScale", &rb->gravityScale, -1.0f, 5.0f);
ImGui::SliderFloat("friction", &rb->friction, 0.0f, 2.0f);
ImGui::SliderFloat("restitution", &rb->restitution, 0.0f, 1.0f);
ImGui::Checkbox("isTrigger", &rb->isTrigger);
// ---- Simulation:动态体物理参数 ----
ImGui::SeparatorText("Simulation");
ImGui::DragFloat("Mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f");
ImGui::SliderFloat("Gravity Scale", &rb->gravityScale, -1.0f, 5.0f);
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 &&
rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic)
{
ImGui::SeparatorText("Debug");
ImGui::Text("vel: %.2f, %.2f, %.2f m/s",
rb->velocity.x, rb->velocity.y, rb->velocity.z);
// 驱动调试(冲量瞬态不序列化):直接推一把看响应
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"))
{
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),
"! requires a RigidBody on this entity");
}
// ---- Constraint:类型与目标体 ----
ImGui::SeparatorText("Constraint");
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);
}
@@ -745,21 +753,25 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
items.push_back('\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)];
}
ImGui::DragFloat3("anchor A", &jc->anchorA.x, 0.05f);
ImGui::DragFloat3("anchor B", &jc->anchorB.x, 0.05f);
// ---- Anchors:各体局部系锚点 ----
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)
{
ImGui::DragFloat3("hinge axis", &jc->hingeAxis.x, 0.05f);
ImGui::Checkbox("enable limit", &jc->enableLimit);
ImGui::SeparatorText("Hinge");
ImGui::DragFloat3("Hinge Axis", &jc->hingeAxis.x, 0.05f);
ImGui::Checkbox("Enable Limit", &jc->enableLimit);
if (jc->enableLimit)
{
ImGui::DragFloat("min deg", &jc->limitMin, 1.0f, -180.0f, 0.0f);
ImGui::DragFloat("max deg", &jc->limitMax, 1.0f, 0.0f, 180.0f);
ImGui::DragFloat("Limit Min", &jc->limitMin, 1.0f, -180.0f, 0.0f);
ImGui::DragFloat("Limit Max", &jc->limitMax, 1.0f, 0.0f, 180.0f);
}
}
if (!jc->active)
@@ -790,16 +802,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
"! conflicts with RigidBody "
"(disable or remove one)");
}
// 实体原点 = 脚底(胶囊底端),总高 = 2*(halfHeight + radius)
ImGui::DragFloat("radius", &cc->radius, 0.02f, 0.05f, 5.0f);
ImGui::DragFloat("halfHeight", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f);
ImGui::DragFloat("mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f");
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);
// ---- Body:胶囊体(实体原点 = 脚底,总高 = 2*(halfHeight + radius))----
ImGui::SeparatorText("Body");
ImGui::DragFloat("Radius", &cc->radius, 0.02f, 0.05f, 5.0f);
ImGui::DragFloat("Half Height", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f);
ImGui::DragFloat("Mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.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())
{
ImGui::SeparatorText("Debug");
const uint32_t bits =
static_cast<uint32_t>(entt::to_integral(selected));
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),
"! requires a MeshRenderer component");
}
else if (model != nullptr)
else if (model == nullptr)
{
// 动画资产槽:列全部模型容器文件(无法静态区分"模型
// fbx"与"纯动画 fbx",一并列出,无 clip 的会警告跳过)
ImGui::TextDisabled("(no model assigned)");
}
else
{
// ---- Clips:动画资产槽(列全部模型容器文件——无法静态
// 区分"模型 fbx"与"纯动画 fbx",一并列出,无 clip 的会
// 警告跳过)----
ImGui::SeparatorText("Clips");
const auto& files = AssetModelFiles();
std::vector<const char*> opts;
opts.push_back("<attach animation file...>");
@@ -1016,9 +1038,15 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
.string()
.c_str());
}
if (!model->HasAnimations())
{
ImGui::TextDisabled("(no animation clips on model)");
}
}
if (model != nullptr && model->HasAnimations())
{
// ---- Playback:播放控制(切换 clip 触发 crossfade)----
ImGui::SeparatorText("Playback");
const auto& clips = model->GetClips();
std::vector<const char*> clipNames;
clipNames.reserve(clips.size());
@@ -1027,16 +1055,16 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
clipNames.push_back(c.name.c_str());
}
int cur = anim->clipIndex;
if (ImGui::Combo("clip", &cur, clipNames.data(),
if (ImGui::Combo("Clip", &cur, clipNames.data(),
static_cast<int>(clipNames.size())))
{
anim->Play(cur); // 触发 crossfade(fade 秒)
}
ImGui::Checkbox("playing", &anim->playing);
ImGui::Checkbox("Playing", &anim->playing);
ImGui::SameLine();
ImGui::Checkbox("loop", &anim->loop);
ImGui::SliderFloat("speed", &anim->speed, -2.0f, 2.0f);
ImGui::SliderFloat("fade", &anim->fadeDuration, 0.0f, 1.0f);
ImGui::Checkbox("Loop", &anim->loop);
ImGui::SliderFloat("Speed", &anim->speed, -2.0f, 2.0f);
ImGui::SliderFloat("Fade", &anim->fadeDuration, 0.0f, 1.0f);
if (anim->clipIndex >= 0 &&
anim->clipIndex < static_cast<int>(clips.size()))
{
@@ -1046,7 +1074,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
// 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的
// 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼
// 命名不同源被跳过)
if (ImGui::TreeNode("clip data"))
if (ImGui::TreeNode("Clip Data"))
{
ImGui::Columns(3, nullptr, false);
ImGui::SetColumnWidth(0, 150.0f);
@@ -1083,10 +1111,6 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::TreePop();
}
}
else
{
ImGui::TextDisabled("(no animation clips on model)");
}
if (!anim->active)
{
ImGui::EndDisabled();
+54 -60
View File
@@ -1,5 +1,8 @@
//
// StatsPanel.cpp:遥测与全局设置实现(原 main.cpp Scene Debug 搬迁)。
// 面板按 View / Scene / Rendering / Physics / Performance 分组——
// 环境贴图/环境光已实体化(Skybox/SkyLight 组件,Details 配置),
// 此处只留场景级 ClearColor。
//
#include "StatsPanel.h"
@@ -19,12 +22,23 @@ void StatsPanel::Draw(const EditorContext& ctx)
auto& renderer = *ctx.renderer;
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();
ImGui::Text("Editor camera pos");
ImGui::Text("Editor Camera Pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
}
else
@@ -34,7 +48,7 @@ void StatsPanel::Draw(const EditorContext& ctx)
{
// world 位姿(相机实体可能挂父层级下)
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);
}
else
@@ -42,35 +56,26 @@ void StatsPanel::Draw(const EditorContext& ctx)
ImGui::TextDisabled("(no primary camera)");
}
}
ImGui::Separator();
}
ImGui::Separator();
static const char* displayTarget[] = {
"Fullscreen",
"Viewport",
};
int mode = renderer.GetDisplayMode();
if (ImGui::Combo("##DisplayTarget", &mode, displayTarget, IM_ARRAYSIZE(displayTarget))) {
renderer.SetDisplayMode(mode);
}
ImGui::Separator();
// ---- Scene:暂停模拟(引擎 Time 门面:动画/物理统一冻结)----
// ---- Scene:模拟控制 + 场景级设置 ----
if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
{
// 暂停模拟(引擎 Time 门面:动画/物理统一冻结)
bool paused = Time::TimeScale() == 0.0f;
if (ImGui::Checkbox("Pause Simulation", &paused))
{
Time::SetTimeScale(paused ? 0.0f : 1.0f);
}
ImGui::ColorEdit4("Clear Color",
glm::value_ptr(ctx.scene->GetSettings().ClearColor));
}
// ---- Rendering:渲染管线参数与开关 ----
if (ImGui::CollapsingHeader("Rendering", ImGuiTreeNodeFlags_DefaultOpen))
{
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);
}
@@ -89,12 +94,12 @@ void StatsPanel::Draw(const EditorContext& ctx)
if (bloom)
{
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);
}
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);
}
@@ -118,14 +123,6 @@ void StatsPanel::Draw(const EditorContext& ctx)
{
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)
const auto& viewNames = renderer.GetDebugViewNames();
@@ -137,13 +134,13 @@ void StatsPanel::Draw(const EditorContext& ctx)
}
}
// ---- Physics Debug:物理调试可视化(collider/约束/重力/速度/加速度)----
if (ImGui::CollapsingHeader("Physics Debug"))
// ---- Physics:调试可视化(collider/约束/重力/速度/加速度)----
if (ImGui::CollapsingHeader("Physics"))
{
auto& physics = app.GetPhysics();
u32 bits = physics.GetDebugDrawBits();
bool master = bits != 0;
if (ImGui::Checkbox("Enable##PhysDebug", &master))
if (ImGui::Checkbox("Debug Draw##Phys", &master))
{
bits = master ? kPhyDbgDefault : 0;
}
@@ -169,9 +166,32 @@ void StatsPanel::Draw(const EditorContext& ctx)
ImGui::Text("contacts (frame): %zu", physics.GetEvents().size());
}
// ---- GPU Timings:只读遥测(默认折叠,不占面板高度)----
if (ImGui::CollapsingHeader("GPU Timings"))
// ---- Performance:帧率控制 + GPU 遥测(默认折叠,不占面板高度)----
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",
renderer.GetMainInstanceCount(),
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();
}