Fix issue with mouse input for viewport camera (#3210)
* fix for drift accumulating in the viewport camera Signed-off-by: hultonha <hultonha@amazon.co.uk> * fix typo and update how events are stored Signed-off-by: hultonha <hultonha@amazon.co.uk> * respond to PR feedback and fix linux and windows build issues Signed-off-by: hultonha <hultonha@amazon.co.uk> * fix failing unit tests in camera input Signed-off-by: hultonha <hultonha@amazon.co.uk>
This commit is contained in:
@@ -242,6 +242,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
Legacy::CryCommon
|
||||
AZ::AzToolsFramework
|
||||
AZ::AzToolsFramework.Tests
|
||||
AZ::AzFrameworkTestShared
|
||||
AZ::AzToolsFrameworkTestCommon
|
||||
Legacy::EditorLib
|
||||
Gem::AtomToolsFramework.Static
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace SandboxEditor
|
||||
constexpr AZStd::string_view CameraPanSpeedSetting = "/Amazon/Preferences/Editor/Camera/PanSpeed";
|
||||
constexpr AZStd::string_view CameraRotateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothness";
|
||||
constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness";
|
||||
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
|
||||
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
|
||||
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
|
||||
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
|
||||
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
|
||||
@@ -259,6 +261,26 @@ namespace SandboxEditor
|
||||
SetRegistry(CameraTranslateSmoothnessSetting, smoothness);
|
||||
}
|
||||
|
||||
bool CameraRotateSmoothingEnabled()
|
||||
{
|
||||
return GetRegistry(CameraRotateSmoothingSetting, true);
|
||||
}
|
||||
|
||||
void SetCameraRotateSmoothingEnabled(const bool enabled)
|
||||
{
|
||||
SetRegistry(CameraRotateSmoothingSetting, enabled);
|
||||
}
|
||||
|
||||
bool CameraTranslateSmoothingEnabled()
|
||||
{
|
||||
return GetRegistry(CameraTranslateSmoothingSetting, true);
|
||||
}
|
||||
|
||||
void SetCameraTranslateSmoothingEnabled(const bool enabled)
|
||||
{
|
||||
SetRegistry(CameraTranslateSmoothingSetting, enabled);
|
||||
}
|
||||
|
||||
AzFramework::InputChannelId CameraTranslateForwardChannelId()
|
||||
{
|
||||
return AzFramework::InputChannelId(
|
||||
|
||||
@@ -80,6 +80,12 @@ namespace SandboxEditor
|
||||
SANDBOX_API float CameraTranslateSmoothness();
|
||||
SANDBOX_API void SetCameraTranslateSmoothness(float smoothness);
|
||||
|
||||
SANDBOX_API bool CameraRotateSmoothingEnabled();
|
||||
SANDBOX_API void SetCameraRotateSmoothingEnabled(bool enabled);
|
||||
|
||||
SANDBOX_API bool CameraTranslateSmoothingEnabled();
|
||||
SANDBOX_API void SetCameraTranslateSmoothingEnabled(bool enabled);
|
||||
|
||||
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
|
||||
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);
|
||||
|
||||
|
||||
@@ -132,12 +132,11 @@ namespace AZ::ViewportHelpers
|
||||
{
|
||||
static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded.";
|
||||
|
||||
class EditorEntityNotifications
|
||||
: public AzToolsFramework::EditorEntityContextNotificationBus::Handler
|
||||
class EditorEntityNotifications : public AzToolsFramework::EditorEntityContextNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
EditorEntityNotifications(EditorViewportWidget& renderViewport)
|
||||
: m_renderViewport(renderViewport)
|
||||
EditorEntityNotifications(EditorViewportWidget& editorViewportWidget)
|
||||
: m_editorViewportWidget(editorViewportWidget)
|
||||
{
|
||||
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
@@ -147,22 +146,24 @@ namespace AZ::ViewportHelpers
|
||||
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
// AzToolsFramework::EditorEntityContextNotificationBus
|
||||
// AzToolsFramework::EditorEntityContextNotificationBus overrides ...
|
||||
void OnStartPlayInEditor() override
|
||||
{
|
||||
m_renderViewport.OnStartPlayInEditor();
|
||||
m_editorViewportWidget.OnStartPlayInEditor();
|
||||
}
|
||||
|
||||
void OnStopPlayInEditor() override
|
||||
{
|
||||
m_renderViewport.OnStopPlayInEditor();
|
||||
m_editorViewportWidget.OnStopPlayInEditor();
|
||||
}
|
||||
|
||||
void OnStartPlayInEditorBegin() override
|
||||
{
|
||||
m_renderViewport.OnStartPlayInEditorBegin();
|
||||
m_editorViewportWidget.OnStartPlayInEditorBegin();
|
||||
}
|
||||
|
||||
private:
|
||||
EditorViewportWidget& m_renderViewport;
|
||||
EditorViewportWidget& m_editorViewportWidget;
|
||||
};
|
||||
} // namespace AZ::ViewportHelpers
|
||||
|
||||
@@ -1027,10 +1028,16 @@ bool EditorViewportWidget::ShowingWorldSpace()
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController(
|
||||
AzFramework::ViewportId viewportId)
|
||||
const AzFramework::ViewportId viewportId)
|
||||
{
|
||||
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
|
||||
|
||||
controller->SetCameraViewportContextBuilderCallback(
|
||||
[viewportId](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
|
||||
{
|
||||
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::ModularCameraViewportContextImpl>(viewportId);
|
||||
});
|
||||
|
||||
controller->SetCameraPriorityBuilderCallback(
|
||||
[](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn)
|
||||
{
|
||||
@@ -1049,6 +1056,16 @@ AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateMod
|
||||
{
|
||||
return SandboxEditor::CameraTranslateSmoothness();
|
||||
};
|
||||
|
||||
cameraProps.m_rotateSmoothingEnabledFn = []
|
||||
{
|
||||
return SandboxEditor::CameraRotateSmoothingEnabled();
|
||||
};
|
||||
|
||||
cameraProps.m_translateSmoothingEnabledFn = []
|
||||
{
|
||||
return SandboxEditor::CameraTranslateSmoothingEnabled();
|
||||
};
|
||||
});
|
||||
|
||||
controller->SetCameraListBuilderCallback(
|
||||
|
||||
@@ -54,7 +54,8 @@ namespace AZ::ViewportHelpers
|
||||
namespace AtomToolsFramework
|
||||
{
|
||||
class RenderViewportWidget;
|
||||
}
|
||||
class ModularViewportCameraController;
|
||||
} // namespace AtomToolsFramework
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
@@ -389,3 +390,7 @@ private:
|
||||
|
||||
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
};
|
||||
|
||||
//! Creates a modular camera controller in the configuration used by the editor viewport.
|
||||
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController(
|
||||
const AzFramework::ViewportId viewportId);
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
|
||||
#include <AzFramework/Viewport/ViewportControllerList.h>
|
||||
#include <AzToolsFramework/Input/QtEventToAzInputManager.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <EditorViewportWidget.h>
|
||||
#include <Mocks/MockWindowRequests.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
const QSize WidgetSize = QSize(1920, 1080);
|
||||
|
||||
using AzToolsFramework::ViewportInteraction::MouseInteractionEvent;
|
||||
|
||||
class ModularViewportCameraControllerFixture : public AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
static const AzFramework::ViewportId TestViewportId;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
m_rootWidget = AZStd::make_unique<QWidget>();
|
||||
m_rootWidget->setFixedSize(WidgetSize);
|
||||
|
||||
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
|
||||
m_controllerList->RegisterViewportContext(TestViewportId);
|
||||
|
||||
m_inputChannelMapper = AZStd::make_unique<AzToolsFramework::QtEventToAzInputMapper>(m_rootWidget.get(), TestViewportId);
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
m_inputChannelMapper.reset();
|
||||
|
||||
m_controllerList->UnregisterViewportContext(TestViewportId);
|
||||
m_controllerList.reset();
|
||||
m_rootWidget.reset();
|
||||
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<QWidget> m_rootWidget;
|
||||
AzFramework::ViewportControllerListPtr m_controllerList;
|
||||
AZStd::unique_ptr<AzToolsFramework::QtEventToAzInputMapper> m_inputChannelMapper;
|
||||
};
|
||||
|
||||
const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0);
|
||||
|
||||
class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext
|
||||
{
|
||||
public:
|
||||
AZ::Transform GetCameraTransform() const override
|
||||
{
|
||||
return m_cameraTransform;
|
||||
}
|
||||
|
||||
void SetCameraTransform(const AZ::Transform& transform) override
|
||||
{
|
||||
m_cameraTransform = transform;
|
||||
}
|
||||
|
||||
void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity();
|
||||
};
|
||||
|
||||
TEST_F(ModularViewportCameraControllerFixture, Mouse_movement_does_not_accumulate_excessive_drift_in_modular_viewport_camera)
|
||||
{
|
||||
AzFramework::NativeWindowHandle nativeWindowHandle = nullptr;
|
||||
|
||||
const float deltaTime = 1.0f / 60.0f; // mimic 60fps
|
||||
|
||||
// Given
|
||||
// listen for events signaled from QtEventToAzInputMapper and forward to the controller list
|
||||
QObject::connect(
|
||||
m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(),
|
||||
[this, nativeWindowHandle](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event)
|
||||
{
|
||||
m_controllerList->HandleInputChannelEvent(
|
||||
AzFramework::ViewportControllerInputEvent{ TestViewportId, nativeWindowHandle, *inputChannel });
|
||||
});
|
||||
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
NiceMock<MockWindowRequests> mockWindowRequests;
|
||||
mockWindowRequests.Connect(nativeWindowHandle);
|
||||
|
||||
// note: WindowRequests is used internally by ModularViewportCameraController, this ensures it returns the viewport size we want
|
||||
ON_CALL(mockWindowRequests, GetClientAreaSize())
|
||||
.WillByDefault(Return(AzFramework::WindowSize(WidgetSize.width(), WidgetSize.height())));
|
||||
|
||||
// create editor modular camera
|
||||
auto controller = CreateModularViewportCameraController(TestViewportId);
|
||||
|
||||
// set some overrides for the test
|
||||
AtomToolsFramework::ModularCameraViewportContext* cameraViewportContextView = nullptr;
|
||||
controller->SetCameraViewportContextBuilderCallback(
|
||||
[&cameraViewportContextView](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
|
||||
{
|
||||
cameraViewportContext = AZStd::make_unique<TestModularCameraViewportContextImpl>();
|
||||
cameraViewportContextView = cameraViewportContext.get();
|
||||
});
|
||||
|
||||
controller->SetCameraPropsBuilderCallback(
|
||||
[](AzFramework::CameraProps& cameraProps)
|
||||
{
|
||||
cameraProps.m_rotateSmoothingEnabledFn = []
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
cameraProps.m_translateSmoothingEnabledFn = []
|
||||
{
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
m_controllerList->Add(controller);
|
||||
|
||||
// move to the center of the screen
|
||||
auto start = QPoint(WidgetSize.width() / 2, WidgetSize.height() / 2);
|
||||
MouseMove(m_rootWidget.get(), start, QPoint(0, 0));
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
|
||||
// When
|
||||
// move mouse diagonally to top right, then to bottom left and back repeatedly
|
||||
auto current = start;
|
||||
auto halfDelta = QPoint(200, -200);
|
||||
const int iterationsPerDiagonal = 50;
|
||||
for (int diagonals = 0; diagonals < 80; ++diagonals)
|
||||
{
|
||||
for (int i = 0; i < iterationsPerDiagonal; ++i)
|
||||
{
|
||||
MousePressAndMove(m_rootWidget.get(), current, halfDelta / iterationsPerDiagonal, Qt::MouseButton::RightButton);
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
current += halfDelta / iterationsPerDiagonal;
|
||||
}
|
||||
|
||||
if (diagonals % 2 == 0)
|
||||
{
|
||||
halfDelta.setX(halfDelta.x() * -1);
|
||||
halfDelta.setY(halfDelta.y() * -1);
|
||||
}
|
||||
}
|
||||
|
||||
QTest::mouseRelease(m_rootWidget.get(), Qt::MouseButton::RightButton, Qt::KeyboardModifier::NoModifier, current);
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
|
||||
// Then
|
||||
// ensure the camera rotation is the identity (no significant drift has occurred as we moved the mouse)
|
||||
const AZ::Transform cameraRotation = cameraViewportContextView->GetCameraTransform();
|
||||
EXPECT_THAT(cameraRotation.GetRotation(), IsClose(AZ::Quaternion::CreateIdentity()));
|
||||
|
||||
mockWindowRequests.Disconnect();
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -21,6 +21,7 @@ set(FILES
|
||||
Lib/Tests/test_ViewportTitleDlgPythonBindings.cpp
|
||||
Lib/Tests/test_DisplaySettingsPythonBindings.cpp
|
||||
Lib/Tests/test_ViewportManipulatorController.cpp
|
||||
Lib/Tests/test_ModularViewportCameraController.cpp
|
||||
DisplaySettingsPythonFuncs.cpp
|
||||
DisplaySettingsPythonFuncs.h
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user