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:
@@ -29,3 +29,6 @@
|
||||
[submodule "Lib/JoltPhysics"]
|
||||
path = Lib/JoltPhysics
|
||||
url = https://github.com/jrouwe/JoltPhysics.git
|
||||
[submodule "Lib/ImGuizmo"]
|
||||
path = Lib/ImGuizmo
|
||||
url = https://github.com/CedricGuillemet/ImGuizmo.git
|
||||
|
||||
@@ -65,7 +65,8 @@ void Editor::Draw()
|
||||
if (m_ShowInspector) m_Inspector->Draw(m_Ctx, m_Selected);
|
||||
if (m_ShowViewport)
|
||||
{
|
||||
if (auto picked = m_Viewport->Draw(m_Ctx, m_VpInput)) {
|
||||
if (auto picked = m_Viewport->Draw(m_Ctx, m_VpInput, m_Gizmo,
|
||||
m_Selected)) {
|
||||
SelectEntity(*picked); // Viewport 点击拾取 → 选中联动 Inspector/Hierarchy
|
||||
}
|
||||
}
|
||||
@@ -96,6 +97,9 @@ bool Editor::BlockingKeyboard() const
|
||||
|
||||
bool Editor::BlockingMouse() const
|
||||
{
|
||||
// Gizmo 拖拽中:鼠标归操纵杆,不给游戏侧相机(松手自动恢复)
|
||||
if (m_Gizmo.dragging) return true;
|
||||
|
||||
// 游戏形态:沿用 ImGui 全局判定(UI 隐藏时 WantCaptureMouse=false)
|
||||
if (m_Ctx.renderer &&
|
||||
m_Ctx.renderer->GetDisplayMode() == kDisplayDirect)
|
||||
|
||||
+3
-2
@@ -47,8 +47,8 @@ public:
|
||||
// 键盘:编辑器形态 = Viewport 持焦且不在文本输入中;游戏形态
|
||||
// (Fullscreen)只在文本输入中让位(打字不触发快捷键)
|
||||
bool BlockingKeyboard() const;
|
||||
// 鼠标:编辑器形态 = 悬停 Viewport 才放行;游戏形态沿用
|
||||
// WantCaptureMouse(UI 隐藏时自动放行)
|
||||
// 鼠标:编辑器形态 = 悬停 Viewport 才放行;Gizmo 拖拽中一律拦截
|
||||
// (抓杆时相机不能吃鼠标);游戏形态沿用 WantCaptureMouse
|
||||
bool BlockingMouse() const;
|
||||
|
||||
// 请求退出(弹确认框;窗口关闭事件走这里)
|
||||
@@ -77,6 +77,7 @@ private:
|
||||
EditorContext m_Ctx{};
|
||||
|
||||
ViewportInputState m_VpInput{}; // Viewport 面板 hover/focus(每帧刷新)
|
||||
GizmoState m_Gizmo{}; // 视口操纵杆状态(op/拖拽中)
|
||||
|
||||
std::unique_ptr<HierarchyPanel> m_Hierarchy;
|
||||
std::unique_ptr<InspectorPanel> m_Inspector;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -25,11 +25,24 @@ struct ViewportInputState {
|
||||
bool focused = false; // Viewport 面板持有键盘焦点
|
||||
};
|
||||
|
||||
// Gizmo 交互状态(Editor 持有、ViewportPanel 写):
|
||||
// op 取 ImGuizmo::OPERATION 位掩码(TRANSLATE=7=三轴位或运算,非 0
|
||||
// 起始枚举;0 = 无任何轴位 → Gizmo 完全不绘制),int 存储避免本
|
||||
// 头文件 include ImGuizmo.h(拖入 imgui 宏依赖顺序)
|
||||
// W/E/R 切换(UE 语义);dragging 拉起 Editor::BlockingMouse 门控
|
||||
struct GizmoState {
|
||||
int op = 7; // ImGuizmo::TRANSLATE(TRANSLATE_X|Y|Z)
|
||||
bool dragging = false; // 拖拽中(IsUsing)→ 鼠标归 Gizmo 不给游戏侧
|
||||
};
|
||||
|
||||
class ViewportPanel {
|
||||
public:
|
||||
// 返回值:本帧拾取命中的实体(无点击/未命中 = nullopt 或 null);
|
||||
// Editor 据此 SelectEntity(面板不持有选中状态)
|
||||
std::optional<entt::entity> Draw(const EditorContext& ctx, ViewportInputState& out) const;
|
||||
std::optional<entt::entity> Draw(const EditorContext& ctx,
|
||||
ViewportInputState& out,
|
||||
GizmoState& gizmo,
|
||||
entt::entity selected) const;
|
||||
|
||||
private:
|
||||
// Image 区域内的鼠标位置 → NDC → Renderer::RequestPick
|
||||
|
||||
@@ -17,6 +17,16 @@ target_include_directories(imgui PUBLIC
|
||||
# 两个 backend 头文件分别依赖 GLFW / Vulkan 头
|
||||
target_link_libraries(imgui PUBLIC glfw Vulkan::Vulkan)
|
||||
|
||||
# ImGuizmo(视口操纵杆,vendored submodule;新版仓库源码在 src/,
|
||||
# 只编译 ImGuizmo.cpp,GraphEditor/ImCurveEdit 等示例模块不用)
|
||||
# IMGUI_DEFINE_MATH_OPERATORS 必须 PUBLIC:ImGuizmo.cpp 与任何
|
||||
# include 其头文件的 TU 都要在 imgui.h 之前看到该宏(ImVec2 运算符)
|
||||
set(IMGUIZMO_DIR ${CMAKE_SOURCE_DIR}/Lib/ImGuizmo/src)
|
||||
add_library(ImGuizmo STATIC ${IMGUIZMO_DIR}/ImGuizmo.cpp)
|
||||
target_include_directories(ImGuizmo PUBLIC ${IMGUIZMO_DIR})
|
||||
target_compile_definitions(ImGuizmo PUBLIC IMGUI_DEFINE_MATH_OPERATORS)
|
||||
target_link_libraries(ImGuizmo PUBLIC imgui)
|
||||
|
||||
# Tracy(CPU/GPU 帧分析)
|
||||
add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp)
|
||||
target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public)
|
||||
@@ -64,6 +74,7 @@ target_link_libraries(Engine PUBLIC
|
||||
meshoptimizer
|
||||
Jolt::Jolt
|
||||
imgui
|
||||
ImGuizmo
|
||||
tracy)
|
||||
|
||||
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
||||
|
||||
Submodule
+1
Submodule Lib/ImGuizmo added at 18cef5e031
Reference in New Issue
Block a user