#include #include #include #include #include #include #include namespace { ImGuizmo::OPERATION toImGuizmo(GizmoOperation operation) { switch (operation) { case GizmoOperation::Rotate: return ImGuizmo::ROTATE; case GizmoOperation::Scale: return ImGuizmo::SCALE; case GizmoOperation::Translate: break; } return ImGuizmo::TRANSLATE; } } void TransformGizmo::HandleShortcuts() { if (ImGui::GetIO().WantTextInput || ImGuizmo::IsUsing()) { return; } if (ImGui::IsKeyPressed(ImGuiKey_T, false)) { operation = GizmoOperation::Translate; } if (ImGui::IsKeyPressed(ImGuiKey_R, false)) { operation = GizmoOperation::Rotate; } if (ImGui::IsKeyPressed(ImGuiKey_Y, false)) { operation = GizmoOperation::Scale; } if (ImGui::IsKeyPressed(ImGuiKey_L, false)) { space = space == GizmoSpace::World ? GizmoSpace::Local : GizmoSpace::World; } } void TransformGizmo::DrawToolbar() { if (!ImGui::Begin("Gizmo", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::End(); return; } const auto operationButton = [this](const char* label, GizmoOperation buttonOperation) { if (ImGui::RadioButton(label, operation == buttonOperation)) { operation = buttonOperation; } }; operationButton("Move (T)", GizmoOperation::Translate); ImGui::SameLine(); operationButton("Rotate (R)", GizmoOperation::Rotate); ImGui::SameLine(); operationButton("Scale (Y)", GizmoOperation::Scale); if (operation != GizmoOperation::Scale) { bool local = space == GizmoSpace::Local; if (ImGui::Checkbox("Local (L)", &local)) { space = local ? GizmoSpace::Local : GizmoSpace::World; } ImGui::SameLine(); } ImGui::Checkbox("Snap", &snap); if (snap) { ImGui::SetNextItemWidth(80.0f); switch (operation) { case GizmoOperation::Translate: ImGui::DragFloat("Step", &translationSnap, 0.05f, 0.01f, 10.0f, "%.2f m"); break; case GizmoOperation::Rotate: ImGui::DragFloat("Step", &rotationSnapDegrees, 1.0f, 1.0f, 90.0f, "%.0f deg"); break; case GizmoOperation::Scale: ImGui::DragFloat("Step", &scaleSnap, 0.01f, 0.01f, 1.0f, "%.2f"); break; } } ImGui::End(); } bool TransformGizmo::Manipulate(GameObject& object, const Camera& camera, const glm::vec2& viewMin, const glm::vec2& viewSize) { ImGuizmo::BeginFrame(); // Drawn into the window that shows the scene, so it stays on top of it and hovers with it ImGuizmo::SetDrawlist(); ImGuizmo::SetOrthographic(false); ImGuizmo::SetRect(viewMin.x, viewMin.y, viewSize.x, viewSize.y); const glm::mat4 view = camera.GetViewMatrix(); glm::mat4 projection = camera.GetProjectionMatrix(); // ImGuizmo expects OpenGL's y up clip space, the engine flips y for Vulkan projection[1][1] *= -1.0f; Transform& transform = object.GetTransform(); glm::mat4 worldMatrix = transform.GetWorldMatrix(); const float snapStep = operation == GizmoOperation::Translate ? translationSnap : operation == GizmoOperation::Rotate ? rotationSnapDegrees : scaleSnap; const float snapValues[3] = {snapStep, snapStep, snapStep}; const ImGuizmo::MODE mode = operation == GizmoOperation::Scale || space == GizmoSpace::Local ? ImGuizmo::LOCAL : ImGuizmo::WORLD; const bool changed = ImGuizmo::Manipulate( glm::value_ptr(view), glm::value_ptr(projection), toImGuizmo(operation), mode, glm::value_ptr(worldMatrix), nullptr, snap ? snapValues : nullptr); if (!changed) { return false; } glm::vec3 position; glm::quat rotation; glm::vec3 scale; glm::vec3 skew; glm::vec4 perspective; if (!glm::decompose(worldMatrix, scale, rotation, position, skew, perspective)) { return false; } switch (operation) { case GizmoOperation::Translate: transform.SetWorldPosition(position); break; case GizmoOperation::Rotate: transform.SetWorldRotation(rotation); break; case GizmoOperation::Scale: transform.SetWorldScale(scale); break; } object.RefreshPhysicsInHierarchy(); return true; } bool TransformGizmo::IsUsing() const { return ImGuizmo::IsUsing(); } bool TransformGizmo::IsOver() const { return ImGuizmo::IsOver(); }