diff --git a/editor/src/Viewport/IViewportHostService.h b/editor/src/Viewport/IViewportHostService.h index 131d2745..9f5e9c80 100644 --- a/editor/src/Viewport/IViewportHostService.h +++ b/editor/src/Viewport/IViewportHostService.h @@ -33,9 +33,14 @@ struct SceneViewportInput { ImVec2 viewportSize = ImVec2(0.0f, 0.0f); ImVec2 mouseDelta = ImVec2(0.0f, 0.0f); float mouseWheel = 0.0f; + float deltaTime = 0.0f; + float moveForward = 0.0f; + float moveRight = 0.0f; + float moveUp = 0.0f; bool hovered = false; bool focused = false; bool looking = false; + bool fastMove = false; bool orbiting = false; bool panning = false; bool focusSelectionRequested = false; diff --git a/editor/src/Viewport/SceneViewportCameraController.h b/editor/src/Viewport/SceneViewportCameraController.h index 2f448c47..6b004567 100644 --- a/editor/src/Viewport/SceneViewportCameraController.h +++ b/editor/src/Viewport/SceneViewportCameraController.h @@ -19,7 +19,12 @@ struct SceneViewportCameraInputState { float panDeltaX = 0.0f; float panDeltaY = 0.0f; float zoomDelta = 0.0f; + float deltaTime = 0.0f; + float moveForward = 0.0f; + float moveRight = 0.0f; + float moveUp = 0.0f; float viewportHeight = 0.0f; + bool fastMove = false; }; class SceneViewportCameraController { @@ -93,6 +98,23 @@ public: m_position += ((right * -input.panDeltaX) + (up * input.panDeltaY)) * worldUnitsPerPixel; } + if (input.deltaTime > 0.0f && + (std::abs(input.moveForward) > Math::EPSILON || + std::abs(input.moveRight) > Math::EPSILON || + std::abs(input.moveUp) > Math::EPSILON)) { + const Math::Vector3 movement = + GetForward() * input.moveForward + + GetRight() * input.moveRight + + Math::Vector3::Up() * input.moveUp; + if (movement.SqrMagnitude() > Math::EPSILON) { + const float speedMultiplier = input.fastMove ? 4.0f : 1.0f; + const float flySpeed = (std::max)(5.0f, m_distance * 2.0f) * speedMultiplier; + const Math::Vector3 delta = Math::Vector3::Normalize(movement) * flySpeed * input.deltaTime; + m_position += delta; + m_focalPoint += delta; + } + } + if (std::abs(input.zoomDelta) > Math::EPSILON) { const float zoomFactor = std::pow(0.85f, input.zoomDelta); m_distance = std::clamp(m_distance * zoomFactor, 0.5f, 500.0f); @@ -111,7 +133,7 @@ public: private: void ApplyRotationDelta(float deltaX, float deltaY) { m_yawDegrees += deltaX * 0.30f; - m_pitchDegrees = std::clamp(m_pitchDegrees - deltaY * 0.20f, -89.0f, 89.0f); + m_pitchDegrees = std::clamp(m_pitchDegrees + deltaY * 0.20f, -89.0f, 89.0f); } Math::Vector3 GetRight() const { diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index 7645821a..cd755db3 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -91,6 +91,11 @@ public: SceneViewportCameraInputState controllerInput = {}; controllerInput.viewportHeight = input.viewportSize.y; controllerInput.zoomDelta = input.hovered ? input.mouseWheel : 0.0f; + controllerInput.deltaTime = input.deltaTime; + controllerInput.moveForward = input.moveForward; + controllerInput.moveRight = input.moveRight; + controllerInput.moveUp = input.moveUp; + controllerInput.fastMove = input.fastMove; if (input.looking) { controllerInput.lookDeltaX = input.mouseDelta.x; diff --git a/editor/src/panels/SceneViewPanel.cpp b/editor/src/panels/SceneViewPanel.cpp index 6bce0ba0..f637a8be 100644 --- a/editor/src/panels/SceneViewPanel.cpp +++ b/editor/src/panels/SceneViewPanel.cpp @@ -263,15 +263,29 @@ void SceneViewPanel::Render() { SceneViewportInput input = {}; input.viewportSize = content.availableSize; + input.deltaTime = io.DeltaTime; input.hovered = content.hovered; input.focused = content.focused; - input.mouseWheel = content.hovered ? io.MouseWheel : 0.0f; + input.mouseWheel = content.hovered ? -io.MouseWheel : 0.0f; input.looking = m_lookDragging; input.orbiting = m_orbitDragging; input.panning = m_panDragging; + input.fastMove = io.KeyShift; input.focusSelectionRequested = content.focused && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_F, false); + if (m_lookDragging && content.focused && !io.WantTextInput) { + input.moveForward = + (ImGui::IsKeyDown(ImGuiKey_W) ? 1.0f : 0.0f) - + (ImGui::IsKeyDown(ImGuiKey_S) ? 1.0f : 0.0f); + input.moveRight = + (ImGui::IsKeyDown(ImGuiKey_D) ? 1.0f : 0.0f) - + (ImGui::IsKeyDown(ImGuiKey_A) ? 1.0f : 0.0f); + input.moveUp = + (ImGui::IsKeyDown(ImGuiKey_E) ? 1.0f : 0.0f) - + (ImGui::IsKeyDown(ImGuiKey_Q) ? 1.0f : 0.0f); + } + if (m_lookDragging || m_orbitDragging || m_panDragging) { input.mouseDelta = io.MouseDelta; } diff --git a/tests/editor/test_scene_viewport_camera_controller.cpp b/tests/editor/test_scene_viewport_camera_controller.cpp index 04beb515..87a73a06 100644 --- a/tests/editor/test_scene_viewport_camera_controller.cpp +++ b/tests/editor/test_scene_viewport_camera_controller.cpp @@ -47,6 +47,7 @@ TEST(SceneViewportCameraController_Test, LookInputRotatesCameraInPlaceAndKeepsDi const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); + const float initialPitch = controller.GetPitchDegrees(); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; @@ -56,6 +57,7 @@ TEST(SceneViewportCameraController_Test, LookInputRotatesCameraInPlaceAndKeepsDi EXPECT_TRUE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_FALSE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); + EXPECT_LT(controller.GetPitchDegrees(), initialPitch); EXPECT_TRUE(NearlyEqual( controller.GetFocalPoint(), controller.GetPosition() + controller.GetForward() * controller.GetDistance(), @@ -69,6 +71,7 @@ TEST(SceneViewportCameraController_Test, OrbitInputRotatesAroundFocalPointAndKee const Vector3 initialPosition = controller.GetPosition(); const Vector3 initialFocus = controller.GetFocalPoint(); const float initialDistance = controller.GetDistance(); + const float initialPitch = controller.GetPitchDegrees(); SceneViewportCameraInputState input = {}; input.viewportHeight = 720.0f; @@ -78,6 +81,7 @@ TEST(SceneViewportCameraController_Test, OrbitInputRotatesAroundFocalPointAndKee EXPECT_FALSE(NearlyEqual(controller.GetPosition(), initialPosition)); EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); + EXPECT_LT(controller.GetPitchDegrees(), initialPitch); EXPECT_NEAR((controller.GetFocalPoint() - controller.GetPosition()).Magnitude(), initialDistance, 1e-3f); } @@ -105,6 +109,26 @@ TEST(SceneViewportCameraController_Test, PanAndZoomUpdateCameraStateConsistently 1e-3f)); } +TEST(SceneViewportCameraController_Test, FlyInputMovesCameraAndFocalPointTogether) { + SceneViewportCameraController controller; + controller.Reset(); + + const Vector3 initialPosition = controller.GetPosition(); + const Vector3 initialFocus = controller.GetFocalPoint(); + const Vector3 initialOffset = initialFocus - initialPosition; + + SceneViewportCameraInputState input = {}; + input.viewportHeight = 720.0f; + input.deltaTime = 0.5f; + input.moveForward = 1.0f; + input.moveRight = 1.0f; + controller.ApplyInput(input); + + EXPECT_FALSE(NearlyEqual(controller.GetPosition(), initialPosition)); + EXPECT_FALSE(NearlyEqual(controller.GetFocalPoint(), initialFocus)); + EXPECT_TRUE(NearlyEqual(controller.GetFocalPoint() - controller.GetPosition(), initialOffset, 1e-3f)); +} + TEST(SceneViewportCameraController_Test, FocusMovesPivotWithoutChangingDistance) { SceneViewportCameraController controller; controller.Reset();