Files
HelloVulkan/App/editor/InspectorPanel.cpp
T
atdunbg 1449bea86d feat: HitProxy GPU 拾取 + UE 选中描边 + 编辑器拾取交互
- HitProxy 拾取:RG 管线渲染实体 id → host readback,点击帧
  PickEntity 精确命中;拾取按视觉几何(Model 联合包围球射线求交),
  与 Collider 解耦(UE/Unity 编辑器语义),命中打印实体名+碰撞体参数
- 选中描边(UE 语义):SelectionMask pass(R8Unorm 完整投影轮廓,
  无深度测试透墙可见)→ Outline pass(边缘检测 + alpha 叠加,
  必须注册在 Tonemap 后);edgeCut 贴视口边缘;widthPx=6 可调
- shader object 实证修复:VUID-08879 同程序 set layouts 超集一致、
  VUID-10065 push block 4×float 平铺、新 pass 析构销毁 VkShaderEXT
- RG:SideEffect() 保活"产出无 RG 内消费者"的 pass 链
- Editor:层次面板点击选中、Inspector 姿态编辑、Stats 面板拾取信息
- 相机 Update 改 const(姿态写入 Transform 引用,Camera 本身无状态变更)
2026-10-02 20:09:05 +08:00

270 lines
9.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// InspectorPanel.cpp:组件编辑实现。
//
// Transform 编辑必须走 Set* 入口(标脏 → 帧循环统一 Compose 重算
// world),直接改私有成员是编译错误——面板天然遵守引擎约束。
//
#include "InspectorPanel.h"
#include "Engine/ECS/Components.h"
#include "Engine/Renderer/Model.h"
#include <imgui.h>
#include <cstdio>
#include <vector>
namespace
{
// InputText 需要 char 缓冲;提交时写回 std::string
bool InputTextString(const char* label, std::string& str)
{
char buf[256];
std::snprintf(buf, sizeof(buf), "%s", str.c_str());
if (ImGui::InputText(label, buf, sizeof(buf)))
{
str = buf;
return true;
}
return false;
}
}
void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{
ImGui::Begin("Inspector");
auto& reg = ctx.scene->GetRegistry();
if (selected == entt::null || !reg.valid(selected))
{
ImGui::TextDisabled("(no entity selected)");
ImGui::End();
return;
}
// ---- Name ----
if (auto* name = reg.try_get<Engine::ECS::NameComponent>(selected))
{
InputTextString("Name", name->name);
}
ImGui::Separator();
// ---- Add Component ----
if (ImGui::Button("Add Component"))
{
ImGui::OpenPopup("add_component");
}
if (ImGui::BeginPopup("add_component"))
{
if (!reg.all_of<Engine::ECS::LightComponent>(selected) &&
ImGui::MenuItem("Light"))
{
reg.emplace<Engine::ECS::LightComponent>(selected);
}
if (!reg.all_of<Engine::ECS::CameraComponent>(selected) &&
ImGui::MenuItem("Camera"))
{
reg.emplace<Engine::ECS::CameraComponent>(selected);
}
if (!reg.all_of<Engine::ECS::AnimationComponent>(selected) &&
ImGui::MenuItem("Animation"))
{
reg.emplace<Engine::ECS::AnimationComponent>(selected);
}
if (!reg.all_of<Engine::ECS::NameComponent>(selected) &&
ImGui::MenuItem("Name"))
{
reg.emplace<Engine::ECS::NameComponent>(selected);
}
if (!reg.all_of<Engine::ECS::RigidBodyComponent>(selected) &&
ImGui::MenuItem("RigidBody"))
{
reg.emplace<Engine::ECS::RigidBodyComponent>(selected);
}
ImGui::EndPopup();
}
ImGui::Separator();
// ---- Transform ----
if (auto* t = reg.try_get<Engine::ECS::TransformComponent>(selected))
{
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen))
{
glm::vec3 pos = t->GetPosition();
if (ImGui::DragFloat3("position", &pos.x, 0.05f))
{
t->SetPosition(pos);
}
glm::vec3 rot = t->GetRotation(); // 欧拉角(度)
if (ImGui::DragFloat3("rotation", &rot.x, 0.5f))
{
t->SetRotation(rot);
}
glm::vec3 scl = t->GetScale();
if (ImGui::DragFloat3("scale", &scl.x, 0.02f, 0.001f, 100.0f))
{
t->SetScale(scl);
}
}
}
// ---- MeshRenderer ----
if (auto* mr = reg.try_get<Engine::ECS::MeshRendererComponent>(selected))
{
if (ImGui::CollapsingHeader("MeshRenderer",
ImGuiTreeNodeFlags_DefaultOpen))
{
if (const Engine::Model* model = ctx.assets->GetModel(mr->model);
model != nullptr)
{
const auto& clips = model->GetClips();
ImGui::Text("model: valid (%u clips, %u bones)",
static_cast<unsigned>(clips.size()),
static_cast<unsigned>(model->GetBoneCount()));
ImGui::TextDisabled("(mesh asset is read-only here)");
}
else
{
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"model: INVALID (dangling ref)");
}
}
}
// ---- Light ----
if (auto* light = reg.try_get<Engine::ECS::LightComponent>(selected))
{
if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen))
{
int type = static_cast<int>(light->type);
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);
if (light->type == Engine::ECS::LightComponent::Type::Directional)
{
ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f);
ImGui::Checkbox("castShadow", &light->castShadow);
}
else
{
ImGui::DragFloat("radius", &light->radius, 0.1f, 0.0f, 100.0f);
ImGui::TextDisabled("(position = entity Transform)");
}
}
}
// ---- Camera ----
if (auto* cam = reg.try_get<Engine::ECS::CameraComponent>(selected))
{
if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen))
{
if (ImGui::Checkbox("primary", &cam->primary) && cam->primary)
{
// primary 互斥:启用本相机时清掉其余实体的 primary
//(引擎取第一个 primary 作为渲染视角)
reg.view<Engine::ECS::CameraComponent>().each(
[&](entt::entity e, Engine::ECS::CameraComponent& c)
{
if (e != selected)
{
c.primary = false;
}
});
}
}
}
// ---- RigidBody(Jolt 刚体:动态体 Transform 由物理每帧写回)----
// type/collider/mass 变更 → PhysicsWorld 重建 body;其余参数即时生效
if (auto* rb = reg.try_get<Engine::ECS::RigidBodyComponent>(selected))
{
if (ImGui::CollapsingHeader("RigidBody", ImGuiTreeNodeFlags_DefaultOpen))
{
int type = static_cast<int>(rb->type);
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, "Box\0Sphere\0Mesh\0"))
{
rb->collider = static_cast<Engine::ECS::RigidBodyComponent::ColliderType>(collider);
}
if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Box)
{
ImGui::DragFloat3("halfExtents", &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);
}
else
{
// Mesh:跟随 MeshRenderer 视觉网格(Static=精确网格,
// 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("isSensor", &rb->isSensor);
if (rb->bodyValid &&
rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic)
{
ImGui::Text("vel: %.2f, %.2f, %.2f m/s",
rb->velocity.x, rb->velocity.y, rb->velocity.z);
}
}
}
// ---- Animation ----
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
{
if (ImGui::CollapsingHeader("Animation",
ImGuiTreeNodeFlags_DefaultOpen))
{
const auto* mr = reg.try_get<Engine::ECS::MeshRendererComponent>(selected);
const Engine::Model* model =
mr ? ctx.assets->GetModel(mr->model) : nullptr;
if (model != nullptr && model->HasAnimations())
{
const auto& clips = model->GetClips();
std::vector<const char*> clipNames;
clipNames.reserve(clips.size());
for (const auto& c : clips)
{
clipNames.push_back(c.name.c_str());
}
int cur = anim->clipIndex;
if (ImGui::Combo("clip", &cur, clipNames.data(),
static_cast<int>(clipNames.size())))
{
anim->Play(cur); // 触发 crossfade(fade 秒)
}
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);
if (anim->clipIndex >= 0 &&
anim->clipIndex < static_cast<int>(clips.size()))
{
ImGui::Text("t: %.2f / %.2f s", anim->time,
clips[anim->clipIndex].duration);
}
}
else
{
ImGui::TextDisabled("(no animation clips on model)");
}
}
}
ImGui::End();
}