diff --git a/App/editor/InspectorPanel.cpp b/App/editor/InspectorPanel.cpp index 6c24aa6..c8f10a9 100644 --- a/App/editor/InspectorPanel.cpp +++ b/App/editor/InspectorPanel.cpp @@ -28,6 +28,40 @@ namespace } return false; } + + // UE 风变换行:label + 三轴色 DragFloat(轴色对齐 Gizmo:X 红 / + // Y 绿 / Z 蓝)。拖拽粗调;Ctrl+Click 进输入框键入精确值。 + // 任一轴变化返回 true + bool DragVec3Row(const char* label, glm::vec3& v, float speed, + float mn = -3.4e38f, float mx = 3.4e38f) + { + ImGui::TextUnformatted(label); + ImGui::SameLine(72.0f); // 固定标签列宽 → 三行输入框对齐 + ImGui::PushID(label); + + constexpr ImVec4 axisCol[3] = { + {0.72f, 0.24f, 0.24f, 1.00f}, // X 红 + {0.30f, 0.68f, 0.34f, 1.00f}, // Y 绿 + {0.24f, 0.40f, 0.88f, 1.00f}, // Z 蓝 + }; + const float boxW = + (ImGui::GetContentRegionAvail().x + - 2.0f * ImGui::GetStyle().ItemInnerSpacing.x) / 3.0f; + + bool changed = false; + for (int i = 0; i < 3; ++i) + { + ImGui::PushStyleColor(ImGuiCol_FrameBg, axisCol[i]); + ImGui::SetNextItemWidth(boxW); + changed |= ImGui::DragFloat( + i == 0 ? "##x" : i == 1 ? "##y" : "##z", + &v[i], speed, mn, mx, "%.2f"); + ImGui::PopStyleColor(); + if (i < 2) ImGui::SameLine(); + } + ImGui::PopID(); + return changed; + } } void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) @@ -91,17 +125,17 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { glm::vec3 pos = t->GetPosition(); - if (ImGui::DragFloat3("position", &pos.x, 0.05f)) + if (DragVec3Row("position", pos, 0.05f)) { t->SetPosition(pos); } - glm::vec3 rot = t->GetRotation(); // 欧拉角(度) - if (ImGui::DragFloat3("rotation", &rot.x, 0.5f)) + glm::vec3 rot = t->GetRotation(); // 派生欧拉(度) + if (DragVec3Row("rotation", rot, 0.5f)) { t->SetRotation(rot); } glm::vec3 scl = t->GetScale(); - if (ImGui::DragFloat3("scale", &scl.x, 0.02f, 0.001f, 100.0f)) + if (DragVec3Row("scale", scl, 0.02f, 0.001f, 100.0f)) { t->SetScale(scl); } diff --git a/App/editor/ViewportPanel.cpp b/App/editor/ViewportPanel.cpp index b5235bc..734ffd9 100644 --- a/App/editor/ViewportPanel.cpp +++ b/App/editor/ViewportPanel.cpp @@ -24,6 +24,8 @@ #include #include +#include + // GTX 扩展(实验性)需显式开启:matrix_decompose(矩阵→SRT 分解, // 旋转以四元数输出,直接写回 TransformComponent) #define GLM_ENABLE_EXPERIMENTAL @@ -33,6 +35,11 @@ using namespace Engine; // u32 / f32(Engine/Core/Types.h) namespace { + // Ctrl 吸附步长(UE Grid Snap 语义:按住 Ctrl 拖拽才吸附) + constexpr float kSnapTranslate = 0.25f; // 米/格 + constexpr float kSnapRotateDeg = 15.0f; // 度 + constexpr float kSnapScale = 0.1f; // 缩放步进 + // 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 @@ -105,13 +112,15 @@ std::optional ViewportPanel::Draw(const EditorContext &ctx, ImGui::Image((ImTextureID) (intptr_t) sceneTex, size); // ---- W/E/R 切换 Gizmo 模式(UE 语义:W 平移 / E 旋转 / - // R 缩放)。仅 Viewport 持焦时生效——输入框打字(WantTextInput) - // 由 BlockingKeyboard 拦游戏侧,但 ImGui 自身仍收键,故必须 - // 限定持焦面板,避免在 Inspector 拖数值时误切 ---- + // R 缩放),T 切换 local/world 操纵空间。仅 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; + if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace; } // ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)---- @@ -138,11 +147,30 @@ std::optional ViewportPanel::Draw(const EditorContext &ctx, ImGuizmo::SetRect(mn.x, mn.y, size.x, size.y); glm::mat4 m = tr->World(); - // 注:操作空间为 WORLD(UE 默认);snap / local-world - // 切换留 TODO + // 操作空间:local(模型轴,旋转/缩放随物体朝向直观)/ + // world(T 键切换)。无父层级时两种模式 world 矩阵同源, + // decompose 写回链路不变 + const ImGuizmo::MODE mode = + gizmo.localSpace ? ImGuizmo::LOCAL : ImGuizmo::WORLD; + + // Ctrl 按住 → 吸附(UE Grid Snap 语义)。ImGuizmo 约定: + // 平移 snap = 世界单位(逐轴)、旋转 = 弧度、缩放 = 步进 + float snap[3] = {0.0f, 0.0f, 0.0f}; + const bool snapping = ImGui::GetIO().KeyCtrl; + if (snapping) { + if (gizmo.op == ImGuizmo::TRANSLATE) { + snap[0] = snap[1] = snap[2] = kSnapTranslate; + } else if (gizmo.op == ImGuizmo::ROTATE) { + snap[0] = glm::radians(kSnapRotateDeg); + } else if (gizmo.op == ImGuizmo::SCALE) { + snap[0] = snap[1] = snap[2] = kSnapScale; + } + } + ImGuizmo::Manipulate(&view[0][0], &projGL[0][0], static_cast(gizmo.op), - ImGuizmo::WORLD, &m[0][0]); + mode, &m[0][0], nullptr, + snapping ? snap : nullptr); gizmo.dragging = ImGuizmo::IsUsing(); // 拖拽生效:分解 world 矩阵写回 SRT(Set* 标脏 → @@ -162,6 +190,17 @@ std::optional ViewportPanel::Draw(const EditorContext &ctx, } } + // Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行) + { + char tip[64]; + std::snprintf(tip, sizeof(tip), "Space: %s [T] Snap: Ctrl", + gizmo.localSpace ? "Local" : "World"); + const ImVec2 imgMin = ImGui::GetItemRectMin(); + ImGui::GetWindowDrawList()->AddText( + ImVec2(imgMin.x + 8.0f, imgMin.y + 8.0f), + IM_COL32(255, 255, 255, 150), tip); + } + // Dynamic 刚体提示:物理每帧把姿态写回 Transform,拖拽即刻 // 被覆盖(Static/Kinematic 编辑器权威,可直接拖)。 // 用 drawlist 叠加而非 ImGui::TextDisabled:后者在 Image 下方 diff --git a/App/editor/ViewportPanel.h b/App/editor/ViewportPanel.h index 8dc59dd..e0c65f6 100644 --- a/App/editor/ViewportPanel.h +++ b/App/editor/ViewportPanel.h @@ -29,9 +29,12 @@ struct ViewportInputState { // op 取 ImGuizmo::OPERATION 位掩码(TRANSLATE=7=三轴位或运算,非 0 // 起始枚举;0 = 无任何轴位 → Gizmo 完全不绘制),int 存储避免本 // 头文件 include ImGuizmo.h(拖入 imgui 宏依赖顺序) -// W/E/R 切换(UE 语义);dragging 拉起 Editor::BlockingMouse 门控 +// W/E/R 切换 op、T 切换 local/world 空间(UE 语义);dragging 拉起 +// Editor::BlockingMouse 门控 struct GizmoState { int op = 7; // ImGuizmo::TRANSLATE(TRANSLATE_X|Y|Z) + bool localSpace = true; // 操纵空间:true=模型轴(旋转/缩放语义), + // T 键切 world bool dragging = false; // 拖拽中(IsUsing)→ 鼠标归 Gizmo 不给游戏侧 };