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"]
|
[submodule "Lib/JoltPhysics"]
|
||||||
path = Lib/JoltPhysics
|
path = Lib/JoltPhysics
|
||||||
url = https://github.com/jrouwe/JoltPhysics.git
|
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_ShowInspector) m_Inspector->Draw(m_Ctx, m_Selected);
|
||||||
if (m_ShowViewport)
|
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
|
SelectEntity(*picked); // Viewport 点击拾取 → 选中联动 Inspector/Hierarchy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +97,9 @@ bool Editor::BlockingKeyboard() const
|
|||||||
|
|
||||||
bool Editor::BlockingMouse() const
|
bool Editor::BlockingMouse() const
|
||||||
{
|
{
|
||||||
|
// Gizmo 拖拽中:鼠标归操纵杆,不给游戏侧相机(松手自动恢复)
|
||||||
|
if (m_Gizmo.dragging) return true;
|
||||||
|
|
||||||
// 游戏形态:沿用 ImGui 全局判定(UI 隐藏时 WantCaptureMouse=false)
|
// 游戏形态:沿用 ImGui 全局判定(UI 隐藏时 WantCaptureMouse=false)
|
||||||
if (m_Ctx.renderer &&
|
if (m_Ctx.renderer &&
|
||||||
m_Ctx.renderer->GetDisplayMode() == kDisplayDirect)
|
m_Ctx.renderer->GetDisplayMode() == kDisplayDirect)
|
||||||
|
|||||||
+3
-2
@@ -47,8 +47,8 @@ public:
|
|||||||
// 键盘:编辑器形态 = Viewport 持焦且不在文本输入中;游戏形态
|
// 键盘:编辑器形态 = Viewport 持焦且不在文本输入中;游戏形态
|
||||||
// (Fullscreen)只在文本输入中让位(打字不触发快捷键)
|
// (Fullscreen)只在文本输入中让位(打字不触发快捷键)
|
||||||
bool BlockingKeyboard() const;
|
bool BlockingKeyboard() const;
|
||||||
// 鼠标:编辑器形态 = 悬停 Viewport 才放行;游戏形态沿用
|
// 鼠标:编辑器形态 = 悬停 Viewport 才放行;Gizmo 拖拽中一律拦截
|
||||||
// WantCaptureMouse(UI 隐藏时自动放行)
|
// (抓杆时相机不能吃鼠标);游戏形态沿用 WantCaptureMouse
|
||||||
bool BlockingMouse() const;
|
bool BlockingMouse() const;
|
||||||
|
|
||||||
// 请求退出(弹确认框;窗口关闭事件走这里)
|
// 请求退出(弹确认框;窗口关闭事件走这里)
|
||||||
@@ -77,6 +77,7 @@ private:
|
|||||||
EditorContext m_Ctx{};
|
EditorContext m_Ctx{};
|
||||||
|
|
||||||
ViewportInputState m_VpInput{}; // Viewport 面板 hover/focus(每帧刷新)
|
ViewportInputState m_VpInput{}; // Viewport 面板 hover/focus(每帧刷新)
|
||||||
|
GizmoState m_Gizmo{}; // 视口操纵杆状态(op/拖拽中)
|
||||||
|
|
||||||
std::unique_ptr<HierarchyPanel> m_Hierarchy;
|
std::unique_ptr<HierarchyPanel> m_Hierarchy;
|
||||||
std::unique_ptr<InspectorPanel> m_Inspector;
|
std::unique_ptr<InspectorPanel> m_Inspector;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "ViewportPanel.h"
|
#include "ViewportPanel.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <ImGuizmo.h>
|
||||||
|
|
||||||
#include <Engine/ECS/Components.h> // MeshRenderer / Name / RigidBody(日志对照)
|
#include <Engine/ECS/Components.h> // MeshRenderer / Name / RigidBody(日志对照)
|
||||||
#include <Engine/Renderer/Renderer.h> // RequestPick / TakePickResult 门面
|
#include <Engine/Renderer/Renderer.h> // RequestPick / TakePickResult 门面
|
||||||
@@ -21,11 +22,36 @@
|
|||||||
#include <Engine/Core/Log.h> // 命中打印
|
#include <Engine/Core/Log.h> // 命中打印
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#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)
|
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,
|
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));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||||
std::optional<entt::entity> picked;
|
std::optional<entt::entity> picked;
|
||||||
{
|
{
|
||||||
@@ -79,9 +105,83 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
else size.y = avail.x / aspect;
|
else size.y = avail.x / aspect;
|
||||||
ImGui::Image((ImTextureID) (intptr_t) sceneTex, size);
|
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 已含)。
|
// 点击拾取:单击 Image 且未命中 UI 遮挡(IsItemHovered 已含)。
|
||||||
// 结果下一帧经轮询到达(本帧不改变选中)
|
// 抓杆时不出 pick(IsOver 悬停杆/IsUsing 拖拽中)——否则按在
|
||||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
|
// 操纵杆上的点击会穿透发拾取请求
|
||||||
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) &&
|
||||||
|
!ImGuizmo::IsOver() && !ImGuizmo::IsUsing()) {
|
||||||
RequestPickAt(ctx, size);
|
RequestPickAt(ctx, size);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,11 +25,24 @@ struct ViewportInputState {
|
|||||||
bool focused = false; // Viewport 面板持有键盘焦点
|
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 {
|
class ViewportPanel {
|
||||||
public:
|
public:
|
||||||
// 返回值:本帧拾取命中的实体(无点击/未命中 = nullopt 或 null);
|
// 返回值:本帧拾取命中的实体(无点击/未命中 = nullopt 或 null);
|
||||||
// Editor 据此 SelectEntity(面板不持有选中状态)
|
// 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:
|
private:
|
||||||
// Image 区域内的鼠标位置 → NDC → Renderer::RequestPick
|
// Image 区域内的鼠标位置 → NDC → Renderer::RequestPick
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ target_include_directories(imgui PUBLIC
|
|||||||
# 两个 backend 头文件分别依赖 GLFW / Vulkan 头
|
# 两个 backend 头文件分别依赖 GLFW / Vulkan 头
|
||||||
target_link_libraries(imgui PUBLIC glfw Vulkan::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 帧分析)
|
# Tracy(CPU/GPU 帧分析)
|
||||||
add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp)
|
add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp)
|
||||||
target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public)
|
target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public)
|
||||||
@@ -64,6 +74,7 @@ target_link_libraries(Engine PUBLIC
|
|||||||
meshoptimizer
|
meshoptimizer
|
||||||
Jolt::Jolt
|
Jolt::Jolt
|
||||||
imgui
|
imgui
|
||||||
|
ImGuizmo
|
||||||
tracy)
|
tracy)
|
||||||
|
|
||||||
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
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