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:
hultonha
2021-08-19 09:06:24 +01:00
committed by GitHub
parent 586678a5f9
commit 80e08dd947
18 changed files with 538 additions and 134 deletions
+1
View File
@@ -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
+22
View File
@@ -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(
+6
View File
@@ -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);
+27 -10
View File
@@ -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(
+6 -1
View File
@@ -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
+1
View File
@@ -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
)