feat: Editor Gizmo——ImGuizmo 平移/旋转/缩放操纵杆
- ImGuizmo 接入:Lib/ImGuizmo(src/ 布局)静态库,IMGUI_DEFINE_MATH_OPERATORS - ViewportPanel:Vulkan→GL 投影转换(Y 翻回 + 深度 [-1,1])、 SetDrawlist/SetRect 叠加绘制、decompose 写回 TransformComponent - W/E/R 切换(UE 语义);拖拽中 BlockingMouse 门控(输入归 Gizmo) - Dynamic 刚体选中提示:物理每帧写回,编辑被覆盖 - 修复:GizmoState.op 初值 0 无轴位(ImGuizmo::TRANSLATE=7)导致杆不绘制 - 点击拾取门控:抓杆时(IsOver/IsUsing)不发 pick 请求
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "ViewportPanel.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <ImGuizmo.h>
|
||||
|
||||
#include <Engine/ECS/Components.h> // MeshRenderer / Name / RigidBody(日志对照)
|
||||
#include <Engine/Renderer/Renderer.h> // RequestPick / TakePickResult 门面
|
||||
@@ -21,11 +22,36 @@
|
||||
#include <Engine/Core/Log.h> // 命中打印
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
// GTX 扩展(实验性)需显式开启:matrix_decompose(矩阵→SRT 分解)、
|
||||
// quaternion(eulerAngles 四元数→欧拉角)
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
using namespace Engine; // u32 / f32(Engine/Core/Types.h)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Vulkan proj → ImGuizmo 期望的 OpenGL 形式(Y-up、Z∈[-1,1]):
|
||||
// Y 行翻回(抵消 Vulkan clip Y 向下翻转);z 行 = 2*z - w 即
|
||||
// 深度 [0,1] → [-1,1]。glm 列主序 m[col][row],逐列改 row1/row2
|
||||
glm::mat4 ProjVulkanToGL(const glm::mat4& projVulkan)
|
||||
{
|
||||
glm::mat4 p = projVulkan;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
p[c][1] *= -1.0f;
|
||||
p[c][2] = 2.0f * p[c][2] - p[c][3];
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
ViewportInputState &out) const {
|
||||
ViewportInputState &out,
|
||||
GizmoState &gizmo,
|
||||
const entt::entity selected) const {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
std::optional<entt::entity> picked;
|
||||
{
|
||||
@@ -79,9 +105,83 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
else size.y = avail.x / aspect;
|
||||
ImGui::Image((ImTextureID) (intptr_t) sceneTex, size);
|
||||
|
||||
// ---- W/E/R 切换 Gizmo 模式(UE 语义:W 平移 / E 旋转 /
|
||||
// R 缩放)。仅 Viewport 持焦时生效——输入框打字(WantTextInput)
|
||||
// 由 BlockingKeyboard 拦游戏侧,但 ImGui 自身仍收键,故必须
|
||||
// 限定持焦面板,避免在 Inspector 拖数值时误切 ----
|
||||
if (out.focused && !ImGui::GetIO().WantTextInput) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
|
||||
}
|
||||
|
||||
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
|
||||
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
|
||||
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
|
||||
auto ® = ctx.scene->GetRegistry();
|
||||
ECS::TransformComponent *tr =
|
||||
selected != entt::null
|
||||
? reg.try_get<ECS::TransformComponent>(selected)
|
||||
: nullptr;
|
||||
if (tr != nullptr) {
|
||||
// 相机矩阵:primary 相机 view + Vulkan proj(转 GL 形式),
|
||||
// 参数与渲染侧一致(aspect = 渲染分辨率、near/far 0.1/100)
|
||||
ECS::TransformComponent *camT = nullptr;
|
||||
if (const auto *camComp = ctx.scene->GetPrimaryCamera(&camT);
|
||||
camComp != nullptr && camT != nullptr) {
|
||||
const glm::mat4 view = camComp->camera.GetView(*camT);
|
||||
const glm::mat4 projGL = ProjVulkanToGL(
|
||||
camComp->camera.GetProjection(aspect, 0.1f, 100.0f));
|
||||
|
||||
ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
|
||||
// 杆画在 Image 区域内(等比适配后的 item rect)
|
||||
const ImVec2 mn = ImGui::GetItemRectMin();
|
||||
ImGuizmo::SetRect(mn.x, mn.y, size.x, size.y);
|
||||
|
||||
glm::mat4 m = tr->World();
|
||||
// 注:操作空间为 WORLD(UE 默认);snap / local-world
|
||||
// 切换留 TODO
|
||||
ImGuizmo::Manipulate(&view[0][0], &projGL[0][0],
|
||||
static_cast<ImGuizmo::OPERATION>(gizmo.op),
|
||||
ImGuizmo::WORLD, &m[0][0]);
|
||||
gizmo.dragging = ImGuizmo::IsUsing();
|
||||
|
||||
// 拖拽生效:分解 world 矩阵写回 SRT(Set* 标脏 →
|
||||
// UpdateTransforms 重算 → 渲染下帧生效)。
|
||||
// eulerAngles 与 Compose 的 YXZ 旋转序在复合旋转下
|
||||
// 可能微差——每帧从 World 重读不累积;表现不对再换
|
||||
// ImGuizmo::DecomposeMatrixToComponents(TODO)
|
||||
if (ImGuizmo::IsUsing()) {
|
||||
glm::vec3 scale, transl, skew;
|
||||
glm::quat rot;
|
||||
glm::vec4 persp;
|
||||
if (glm::decompose(m, scale, rot, transl, skew, persp)) {
|
||||
const glm::vec3 euler =
|
||||
degrees(eulerAngles(rot));
|
||||
tr->SetPosition(transl)
|
||||
.SetRotation(euler)
|
||||
.SetScale(scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic 刚体提示:物理每帧把姿态写回 Transform,拖拽即刻
|
||||
// 被覆盖(Static/Kinematic 编辑器权威,可直接拖)
|
||||
if (selected != entt::null) {
|
||||
if (const auto *rb = reg.try_get<ECS::RigidBodyComponent>(selected);
|
||||
rb != nullptr &&
|
||||
rb->type == ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||
ImGui::TextDisabled(
|
||||
"(Dynamic body: transform 由物理每帧覆盖)");
|
||||
}
|
||||
}
|
||||
|
||||
// 点击拾取:单击 Image 且未命中 UI 遮挡(IsItemHovered 已含)。
|
||||
// 结果下一帧经轮询到达(本帧不改变选中)
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
|
||||
// 抓杆时不出 pick(IsOver 悬停杆/IsUsing 拖拽中)——否则按在
|
||||
// 操纵杆上的点击会穿透发拾取请求
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) &&
|
||||
!ImGuizmo::IsOver() && !ImGuizmo::IsUsing()) {
|
||||
RequestPickAt(ctx, size);
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user