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
|
||||
)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#include "CameraInput.h"
|
||||
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
#include <AzCore/Math/Plane.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
@@ -26,6 +26,13 @@ namespace AzFramework
|
||||
"The default height of the ground plane to do intersection tests against when orbiting");
|
||||
AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(
|
||||
bool,
|
||||
ed_cameraSystemUseCursor,
|
||||
true,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"Should the camera use cursor absolute positions or motion deltas");
|
||||
|
||||
//! return -1.0f if inverted, 1.0f otherwise
|
||||
constexpr static float Invert(const bool invert)
|
||||
@@ -134,9 +141,13 @@ namespace AzFramework
|
||||
|
||||
bool CameraSystem::HandleEvents(const InputEvent& event)
|
||||
{
|
||||
if (const auto& horizonalMotion = AZStd::get_if<HorizontalMotionEvent>(&event))
|
||||
if (const auto& cursor = AZStd::get_if<CursorEvent>(&event))
|
||||
{
|
||||
m_motionDelta.m_x = horizonalMotion->m_delta;
|
||||
m_cursorState.SetCurrentPosition(cursor->m_position);
|
||||
}
|
||||
else if (const auto& horizontalMotion = AZStd::get_if<HorizontalMotionEvent>(&event))
|
||||
{
|
||||
m_motionDelta.m_x = horizontalMotion->m_delta;
|
||||
}
|
||||
else if (const auto& verticalMotion = AZStd::get_if<VerticalMotionEvent>(&event))
|
||||
{
|
||||
@@ -147,15 +158,18 @@ namespace AzFramework
|
||||
m_scrollDelta = scroll->m_delta;
|
||||
}
|
||||
|
||||
m_handlingEvents = m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
|
||||
m_handlingEvents =
|
||||
m_cameras.HandleEvents(event, ed_cameraSystemUseCursor ? m_cursorState.CursorDelta() : m_motionDelta, m_scrollDelta);
|
||||
|
||||
return m_handlingEvents;
|
||||
}
|
||||
|
||||
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
|
||||
{
|
||||
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
|
||||
const auto nextCamera = m_cameras.StepCamera(
|
||||
targetCamera, ed_cameraSystemUseCursor ? m_cursorState.CursorDelta() : m_motionDelta, m_scrollDelta, deltaTime);
|
||||
|
||||
m_cursorState.Update();
|
||||
m_motionDelta = ScreenVector{ 0, 0 };
|
||||
m_scrollDelta = 0.0f;
|
||||
|
||||
@@ -727,18 +741,36 @@ namespace AzFramework
|
||||
Camera camera;
|
||||
// note: the math for the lerp smoothing implementation for camera rotation and translation was inspired by this excellent
|
||||
// article by Scott Lembcke: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php
|
||||
const float lookRate = AZStd::exp2(cameraProps.m_rotateSmoothnessFn());
|
||||
const float lookT = AZStd::exp2(-lookRate * deltaTime);
|
||||
camera.m_pitch = AZ::Lerp(targetCamera.m_pitch, currentCamera.m_pitch, lookT);
|
||||
camera.m_yaw = AZ::Lerp(targetYaw, currentYaw, lookT);
|
||||
const float moveRate = AZStd::exp2(cameraProps.m_translateSmoothnessFn());
|
||||
const float moveT = AZStd::exp2(-moveRate * deltaTime);
|
||||
camera.m_lookDist = AZ::Lerp(targetCamera.m_lookDist, currentCamera.m_lookDist, moveT);
|
||||
camera.m_lookAt = targetCamera.m_lookAt.Lerp(currentCamera.m_lookAt, moveT);
|
||||
if (cameraProps.m_rotateSmoothingEnabledFn())
|
||||
{
|
||||
const float lookRate = AZStd::exp2(cameraProps.m_rotateSmoothnessFn());
|
||||
const float lookTime = AZStd::exp2(-lookRate * deltaTime);
|
||||
camera.m_pitch = AZ::Lerp(targetCamera.m_pitch, currentCamera.m_pitch, lookTime);
|
||||
camera.m_yaw = AZ::Lerp(targetYaw, currentYaw, lookTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.m_pitch = targetCamera.m_pitch;
|
||||
camera.m_yaw = targetYaw;
|
||||
}
|
||||
|
||||
if (cameraProps.m_translateSmoothingEnabledFn())
|
||||
{
|
||||
const float moveRate = AZStd::exp2(cameraProps.m_translateSmoothnessFn());
|
||||
const float moveTime = AZStd::exp2(-moveRate * deltaTime);
|
||||
camera.m_lookDist = AZ::Lerp(targetCamera.m_lookDist, currentCamera.m_lookDist, moveTime);
|
||||
camera.m_lookAt = targetCamera.m_lookAt.Lerp(currentCamera.m_lookAt, moveTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.m_lookDist = targetCamera.m_lookDist;
|
||||
camera.m_lookAt = targetCamera.m_lookAt;
|
||||
}
|
||||
|
||||
return camera;
|
||||
}
|
||||
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel)
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
|
||||
{
|
||||
const auto& inputChannelId = inputChannel.GetInputChannelId();
|
||||
const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
|
||||
@@ -753,7 +785,16 @@ namespace AzFramework
|
||||
// accept active mouse channel updates, inactive movement channels will just have a 0 delta
|
||||
if (inputChannel.IsActive())
|
||||
{
|
||||
if (inputChannelId == InputDeviceMouse::Movement::X)
|
||||
if (inputChannelId == InputDeviceMouse::SystemCursorPosition)
|
||||
{
|
||||
const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
|
||||
AZ_Assert(position, "Expected PositionData2D but found nullptr");
|
||||
|
||||
return CursorEvent{ ScreenPoint(
|
||||
position->m_normalizedPosition.GetX() * windowSize.m_width,
|
||||
position->m_normalizedPosition.GetY() * windowSize.m_height) };
|
||||
}
|
||||
else if (inputChannelId == InputDeviceMouse::Movement::X)
|
||||
{
|
||||
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
|
||||
}
|
||||
@@ -761,6 +802,7 @@ namespace AzFramework
|
||||
{
|
||||
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
|
||||
}
|
||||
|
||||
else if (inputChannelId == InputDeviceMouse::Movement::Z)
|
||||
{
|
||||
return ScrollEvent{ inputChannel.GetValue() };
|
||||
|
||||
@@ -8,17 +8,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/Input/Channels/InputChannel.h>
|
||||
#include <AzFramework/Viewport/ClickDetector.h>
|
||||
#include <AzFramework/Viewport/CursorState.h>
|
||||
#include <AzFramework/Viewport/ScreenGeometry.h>
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
AZ_CVAR_EXTERNED(bool, ed_cameraSystemUseCursor);
|
||||
|
||||
struct WindowSize;
|
||||
|
||||
//! Returns Euler angles (pitch, roll, yaw) for the incoming orientation.
|
||||
//! @note Order of rotation is Z, Y, X.
|
||||
AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation);
|
||||
@@ -79,6 +85,11 @@ namespace AzFramework
|
||||
using HorizontalMotionEvent = MotionEvent<struct HorizontalMotionTag>;
|
||||
using VerticalMotionEvent = MotionEvent<struct VerticalMotionTag>;
|
||||
|
||||
struct CursorEvent
|
||||
{
|
||||
ScreenPoint m_position;
|
||||
};
|
||||
|
||||
struct ScrollEvent
|
||||
{
|
||||
float m_delta;
|
||||
@@ -93,7 +104,8 @@ namespace AzFramework
|
||||
};
|
||||
|
||||
//! Represents a type-safe union of input events that are handled by the camera system.
|
||||
using InputEvent = AZStd::variant<AZStd::monostate, HorizontalMotionEvent, VerticalMotionEvent, ScrollEvent, DiscreteInputEvent>;
|
||||
using InputEvent =
|
||||
AZStd::variant<AZStd::monostate, HorizontalMotionEvent, VerticalMotionEvent, CursorEvent, ScrollEvent, DiscreteInputEvent>;
|
||||
|
||||
//! Base class for all camera behaviors.
|
||||
//! The core interface consists of:
|
||||
@@ -219,10 +231,14 @@ namespace AzFramework
|
||||
//! Properties to use to configure behavior across all types of camera.
|
||||
struct CameraProps
|
||||
{
|
||||
AZStd::function<float()>
|
||||
m_rotateSmoothnessFn; //!< Rotate smoothing value (useful approx range 3-6, higher values give sharper feel).
|
||||
AZStd::function<float()>
|
||||
m_translateSmoothnessFn; //!< Translate smoothing value (useful approx range 3-6, higher values give sharper feel).
|
||||
//! Rotate smoothing value (useful approx range 3-6, higher values give sharper feel).
|
||||
AZStd::function<float()> m_rotateSmoothnessFn;
|
||||
//! Translate smoothing value (useful approx range 3-6, higher values give sharper feel).
|
||||
AZStd::function<float()> m_translateSmoothnessFn;
|
||||
//! Enable/disable rotation smoothing.
|
||||
AZStd::function<bool()> m_rotateSmoothingEnabledFn;
|
||||
//! Enable/disable translation smoothing.
|
||||
AZStd::function<bool()> m_translateSmoothingEnabledFn;
|
||||
};
|
||||
|
||||
//! An interpolation function to smoothly interpolate all camera properties from currentCamera to targetCamera.
|
||||
@@ -262,12 +278,16 @@ namespace AzFramework
|
||||
public:
|
||||
bool HandleEvents(const InputEvent& event);
|
||||
Camera StepCamera(const Camera& targetCamera, float deltaTime);
|
||||
bool HandlingEvents() const { return m_handlingEvents; }
|
||||
bool HandlingEvents() const
|
||||
{
|
||||
return m_handlingEvents;
|
||||
}
|
||||
|
||||
Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller.
|
||||
|
||||
private:
|
||||
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
|
||||
CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta).
|
||||
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
|
||||
bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated).
|
||||
};
|
||||
@@ -548,5 +568,5 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
//! Map from a generic InputChannel event to a camera specific InputEvent.
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel);
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -70,6 +70,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
PUBLIC
|
||||
AZ::AzTest
|
||||
AZ::AzTestShared
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
|
||||
@@ -59,10 +59,15 @@ namespace UnitTest
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(orbitCamera);
|
||||
|
||||
// these tests rely on using motion delta, not cursor positions (default is true)
|
||||
AzFramework::ed_cameraSystemUseCursor = false;
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
AzFramework::ed_cameraSystemUseCursor = true;
|
||||
|
||||
m_firstPersonRotateCamera.reset();
|
||||
m_firstPersonTranslateCamera.reset();
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class MockWindowRequests : public AzFramework::WindowRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
void Connect(AzFramework::NativeWindowHandle handle)
|
||||
{
|
||||
AzFramework::WindowRequestBus::Handler::BusConnect(handle);
|
||||
}
|
||||
void Disconnect()
|
||||
{
|
||||
AzFramework::WindowRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
// AzFramework::WindowRequestBus overrides ...
|
||||
MOCK_METHOD1(SetWindowTitle, void(const AZStd::string&));
|
||||
MOCK_CONST_METHOD0(GetClientAreaSize, AzFramework::WindowSize());
|
||||
MOCK_METHOD1(ResizeClientArea, void(AzFramework::WindowSize clientAreaSize));
|
||||
MOCK_CONST_METHOD0(GetFullScreenState, bool());
|
||||
MOCK_METHOD1(SetFullScreenState, void(bool));
|
||||
MOCK_CONST_METHOD0(CanToggleFullScreenState, bool());
|
||||
MOCK_METHOD0(ToggleFullScreenState, void());
|
||||
MOCK_CONST_METHOD0(GetDpiScaleFactor, float());
|
||||
MOCK_CONST_METHOD0(GetSyncInterval, uint32_t());
|
||||
MOCK_CONST_METHOD0(GetDisplayRefreshRate, uint32_t());
|
||||
};
|
||||
} // namespace UnitTest
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
set(FILES
|
||||
Mocks/MockSpawnableEntitiesInterface.h
|
||||
Mocks/MockWindowRequests.h
|
||||
Utils/Utils.h
|
||||
Utils/Utils.cpp
|
||||
FrameworkApplicationFixture.h
|
||||
|
||||
+17
-21
@@ -285,7 +285,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void QtEventToAzInputMapper::ProcessPendingMouseEvents()
|
||||
void QtEventToAzInputMapper::ProcessPendingMouseEvents(const QPoint& cursorDelta)
|
||||
{
|
||||
auto systemCursorChannel =
|
||||
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::SystemCursorPosition);
|
||||
@@ -297,14 +297,8 @@ namespace AzToolsFramework
|
||||
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::Z);
|
||||
|
||||
systemCursorChannel->ProcessRawInputEvent(m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta.GetLength());
|
||||
// Generate movement events based on the pixel delta divided by the DPI scaling factor, to calculate a rough approximation
|
||||
// of cursor movement velocity.
|
||||
movementXChannel->ProcessRawInputEvent(
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta.GetX() * aznumeric_cast<float>(m_sourceWidget->width()) /
|
||||
m_sourceWidget->devicePixelRatioF());
|
||||
movementYChannel->ProcessRawInputEvent(
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta.GetY() * aznumeric_cast<float>(m_sourceWidget->height()) /
|
||||
m_sourceWidget->devicePixelRatioF());
|
||||
movementXChannel->ProcessRawInputEvent(cursorDelta.x());
|
||||
movementYChannel->ProcessRawInputEvent(cursorDelta.y());
|
||||
mouseWheelChannel->ProcessRawInputEvent(0.0f);
|
||||
|
||||
NotifyUpdateChannelIfNotIdle(systemCursorChannel, nullptr);
|
||||
@@ -337,41 +331,43 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Vector2 QtEventToAzInputMapper::WidgetPositionToNormalizedPosition(QPoint position)
|
||||
AZ::Vector2 QtEventToAzInputMapper::WidgetPositionToNormalizedPosition(const QPoint& position)
|
||||
{
|
||||
const float normalizedX = aznumeric_cast<float>(position.x()) / aznumeric_cast<float>(m_sourceWidget->width());
|
||||
const float normalizedY = aznumeric_cast<float>(position.y()) / aznumeric_cast<float>(m_sourceWidget->height());
|
||||
return AZ::Vector2{normalizedX, normalizedY};
|
||||
return AZ::Vector2{ normalizedX, normalizedY };
|
||||
}
|
||||
|
||||
QPoint QtEventToAzInputMapper::NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition)
|
||||
QPoint QtEventToAzInputMapper::NormalizedPositionToWidgetPosition(const AZ::Vector2& normalizedPosition)
|
||||
{
|
||||
const int denormalizedX = aznumeric_cast<int>(normalizedPosition.GetX() * m_sourceWidget->width());
|
||||
const int denormalizedY = aznumeric_cast<int>(normalizedPosition.GetY() * m_sourceWidget->height());
|
||||
return QPoint{denormalizedX, denormalizedY};
|
||||
return QPoint{ denormalizedX, denormalizedY };
|
||||
}
|
||||
|
||||
void QtEventToAzInputMapper::HandleMouseMoveEvent(QMouseEvent* mouseEvent)
|
||||
{
|
||||
AZ::Vector2 lastCursorPosition = m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition;
|
||||
const QPoint cursorPosition = mouseEvent->pos();
|
||||
const QPoint cursorDelta = cursorPosition - m_previousCursorPosition;
|
||||
|
||||
const QPoint mousePos = mouseEvent->pos();
|
||||
const AZ::Vector2 normalizedPosition = WidgetPositionToNormalizedPosition(mousePos);
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta = normalizedPosition - m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition;
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = normalizedPosition;
|
||||
ProcessPendingMouseEvents();
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(cursorPosition);
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta = WidgetPositionToNormalizedPosition(cursorDelta);
|
||||
|
||||
ProcessPendingMouseEvents(cursorDelta);
|
||||
|
||||
if (m_capturingCursor)
|
||||
{
|
||||
// Reset our cursor position to the previous point.
|
||||
QPoint targetScreenPosition = m_sourceWidget->mapToGlobal(NormalizedPositionToWidgetPosition(lastCursorPosition));
|
||||
const QPoint targetScreenPosition = m_sourceWidget->mapToGlobal(m_previousCursorPosition);
|
||||
AzQtComponents::SetCursorPos(targetScreenPosition);
|
||||
|
||||
// Even though we just set the cursor position, there are edge cases such as remote desktop that will leave
|
||||
// the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation.
|
||||
QPoint actualWidgetPosition = m_sourceWidget->mapFromGlobal(QCursor::pos());
|
||||
const QPoint actualWidgetPosition = m_sourceWidget->mapFromGlobal(QCursor::pos());
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(actualWidgetPosition);
|
||||
}
|
||||
|
||||
m_previousCursorPosition = cursorPosition;
|
||||
}
|
||||
|
||||
void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <QEvent>
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
#endif //! defined(Q_MOC_RUN)
|
||||
|
||||
class QWidget;
|
||||
@@ -111,12 +112,12 @@ namespace AzToolsFramework
|
||||
void NotifyUpdateChannelIfNotIdle(const AzFramework::InputChannel* channel, QEvent* event);
|
||||
|
||||
// Processes any pending mouse movement events, this allows mouse movement channels to close themselves.
|
||||
void ProcessPendingMouseEvents();
|
||||
void ProcessPendingMouseEvents(const QPoint& cursorDelta);
|
||||
|
||||
// Converts a point in logical source widget space [0..m_sourceWidget->size()] to normalized [0..1] space.
|
||||
AZ::Vector2 WidgetPositionToNormalizedPosition(QPoint position);
|
||||
AZ::Vector2 WidgetPositionToNormalizedPosition(const QPoint& position);
|
||||
// Converts a point in normalized [0..1] space to logical source widget space [0..m_sourceWidget->size()].
|
||||
QPoint NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition);
|
||||
QPoint NormalizedPositionToWidgetPosition(const AZ::Vector2& normalizedPosition);
|
||||
|
||||
// Handle mouse click events.
|
||||
void HandleMouseButtonEvent(QMouseEvent* mouseEvent);
|
||||
@@ -148,6 +149,8 @@ namespace AzToolsFramework
|
||||
AZStd::unordered_set<Qt::Key> m_highPriorityKeys;
|
||||
// A lookup table for AZ input channel ID -> physical input channel on our mouse or keyboard device.
|
||||
AZStd::unordered_map<AzFramework::InputChannelId, AzFramework::InputChannel*> m_channels;
|
||||
// Where the position of the mouse cursor was at the last cursor event.
|
||||
QPoint m_previousCursorPosition;
|
||||
// The source widget to map events from, used to calculate the relative mouse position within the widget bounds.
|
||||
QWidget* m_sourceWidget;
|
||||
// Flags whether or not Qt events should currently be processed.
|
||||
|
||||
+7
-16
@@ -188,7 +188,7 @@ namespace AzToolsFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace AzToolsFramework::ViewportInteraction;
|
||||
using AzToolsFramework::ViewportInteraction::MouseEvent;
|
||||
const auto& mouseInteraction = mouseInteractionEvent.m_mouseInteraction;
|
||||
// store the current interaction for use in DrawManipulators
|
||||
m_currentInteraction = mouseInteraction;
|
||||
@@ -196,28 +196,19 @@ namespace AzToolsFramework
|
||||
switch (mouseInteractionEvent.m_mouseEvent)
|
||||
{
|
||||
case MouseEvent::Down:
|
||||
{
|
||||
return m_manipulatorManager->ConsumeViewportMousePress(mouseInteraction);
|
||||
}
|
||||
return m_manipulatorManager->ConsumeViewportMousePress(mouseInteraction);
|
||||
case MouseEvent::DoubleClick:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
case MouseEvent::Move:
|
||||
{
|
||||
AzToolsFramework::ManipulatorManager::ConsumeMouseMoveResult mouseMoveResult =
|
||||
AzToolsFramework::ManipulatorManager::ConsumeMouseMoveResult::None;
|
||||
mouseMoveResult = m_manipulatorManager->ConsumeViewportMouseMove(mouseInteraction);
|
||||
const AzToolsFramework::ManipulatorManager::ConsumeMouseMoveResult mouseMoveResult =
|
||||
m_manipulatorManager->ConsumeViewportMouseMove(mouseInteraction);
|
||||
return mouseMoveResult == AzToolsFramework::ManipulatorManager::ConsumeMouseMoveResult::Interacting;
|
||||
}
|
||||
case MouseEvent::Up:
|
||||
{
|
||||
return m_manipulatorManager->ConsumeViewportMouseRelease(mouseInteraction);
|
||||
}
|
||||
return m_manipulatorManager->ConsumeViewportMouseRelease(mouseInteraction);
|
||||
case MouseEvent::Wheel:
|
||||
{
|
||||
return m_manipulatorManager->ConsumeViewportMouseWheel(mouseInteraction);
|
||||
}
|
||||
return m_manipulatorManager->ConsumeViewportMouseWheel(mouseInteraction);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
+38
@@ -18,9 +18,22 @@ namespace AtomToolsFramework
|
||||
{
|
||||
class ModularViewportCameraControllerInstance;
|
||||
|
||||
//! A reduced ViewportContext interface for use by the ModularViewportCameraController.
|
||||
//! @note This extra indirection is used to facilitate testing the ModularViewportCameraController.
|
||||
class ModularCameraViewportContext
|
||||
{
|
||||
public:
|
||||
virtual ~ModularCameraViewportContext() = default;
|
||||
|
||||
virtual AZ::Transform GetCameraTransform() const = 0;
|
||||
virtual void SetCameraTransform(const AZ::Transform& transform) = 0;
|
||||
virtual void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler) = 0;
|
||||
};
|
||||
|
||||
//! A function object to represent returning a camera controller priority.
|
||||
using CameraControllerPriorityFn =
|
||||
AZStd::function<AzFramework::ViewportControllerPriority(const AzFramework::CameraSystem& cameraSystem)>;
|
||||
using CameraViewportContextFn = AZStd::function<AZStd::unique_ptr<ModularCameraViewportContext>(AzFramework::ViewportId)>;
|
||||
|
||||
//! The default behavior for what priority the camera controller should respond to events at.
|
||||
//! @note This can change based on the state of the camera controller/system.
|
||||
@@ -38,6 +51,7 @@ namespace AtomToolsFramework
|
||||
using CameraListBuilder = AZStd::function<void(AzFramework::Cameras&)>;
|
||||
using CameraPropsBuilder = AZStd::function<void(AzFramework::CameraProps&)>;
|
||||
using CameraPriorityBuilder = AZStd::function<void(CameraControllerPriorityFn&)>;
|
||||
using CameraViewportContextBuilder = AZStd::function<void(AZStd::unique_ptr<ModularCameraViewportContext>&)>;
|
||||
|
||||
//! Sets the camera list builder callback used to populate new ModularViewportCameraControllerInstances.
|
||||
void SetCameraListBuilderCallback(const CameraListBuilder& builder);
|
||||
@@ -45,6 +59,8 @@ namespace AtomToolsFramework
|
||||
void SetCameraPropsBuilderCallback(const CameraPropsBuilder& builder);
|
||||
//! Sets the camera controller priority builder callback used to populate new ModularViewportCameraControllerInstances.
|
||||
void SetCameraPriorityBuilderCallback(const CameraPriorityBuilder& builder);
|
||||
//! Sets the camera controller viewport context builder callback to populate new ModularViewportCameraControllerInstances.
|
||||
void SetCameraViewportContextBuilderCallback(const CameraViewportContextBuilder& builder);
|
||||
|
||||
private:
|
||||
//! Sets up a camera list based on this controller's CameraListBuilderCallback.
|
||||
@@ -53,6 +69,8 @@ namespace AtomToolsFramework
|
||||
void SetupCameraProperties(AzFramework::CameraProps& cameraProps);
|
||||
//! Sets up how the camera controller should decide at what priority level to respond to.
|
||||
void SetupCameraControllerPriority(CameraControllerPriorityFn& cameraPriorityFn);
|
||||
//! Sets up what viewport context should be used by the camera controller.
|
||||
void SetupCameraControllerViewportContext(AZStd::unique_ptr<ModularCameraViewportContext>& cameraViewportContext);
|
||||
|
||||
//! Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance.
|
||||
CameraListBuilder m_cameraListBuilder;
|
||||
@@ -60,6 +78,24 @@ namespace AtomToolsFramework
|
||||
CameraPropsBuilder m_cameraPropsBuilder;
|
||||
//! Builder to define what priority level the camera controller should respond to events at.
|
||||
CameraPriorityBuilder m_cameraControllerPriorityBuilder;
|
||||
//! Builder to define what viewport context interface the camera controller should use.
|
||||
CameraViewportContextBuilder m_cameraViewportContextBuilder;
|
||||
};
|
||||
|
||||
//! The production modular camera viewport context backed by an AZ::RPI::ViewportContextPtr.
|
||||
//! @note This is instantiated during normal runtime use.
|
||||
class ModularCameraViewportContextImpl : public ModularCameraViewportContext
|
||||
{
|
||||
public:
|
||||
explicit ModularCameraViewportContextImpl(AzFramework::ViewportId viewportId);
|
||||
|
||||
// ModularCameraViewportContext overrides ...
|
||||
AZ::Transform GetCameraTransform() const override;
|
||||
void SetCameraTransform(const AZ::Transform& transform) override;
|
||||
void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler) override;
|
||||
|
||||
private:
|
||||
AzFramework::ViewportId m_viewportId;
|
||||
};
|
||||
|
||||
//! A customizable camera controller that can be configured to run a varying set of CameraInput instances.
|
||||
@@ -115,5 +151,7 @@ namespace AtomToolsFramework
|
||||
bool m_updatingTransformInternally = false;
|
||||
//! Listen for camera view changes outside of the camera controller.
|
||||
AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler;
|
||||
//! The current instance of the modular camera viewport context.
|
||||
AZStd::unique_ptr<ModularCameraViewportContext> m_modularCameraViewportContext;
|
||||
};
|
||||
} // namespace AtomToolsFramework
|
||||
|
||||
+103
-61
@@ -41,6 +41,7 @@ namespace AtomToolsFramework
|
||||
display.DrawLine(transform.GetTranslation(), transform.GetTranslation() + transform.GetBasisZ().GetNormalizedSafe() * axisLength);
|
||||
}
|
||||
|
||||
// convenience function to access the ViewportContext for the given ViewportId.
|
||||
static AZ::RPI::ViewportContextPtr RetrieveViewportContext(const AzFramework::ViewportId viewportId)
|
||||
{
|
||||
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
|
||||
@@ -58,6 +59,35 @@ namespace AtomToolsFramework
|
||||
return viewportContext;
|
||||
}
|
||||
|
||||
ModularCameraViewportContextImpl::ModularCameraViewportContextImpl(const AzFramework::ViewportId viewportId)
|
||||
: m_viewportId(viewportId)
|
||||
{
|
||||
}
|
||||
|
||||
AZ::Transform ModularCameraViewportContextImpl::GetCameraTransform() const
|
||||
{
|
||||
if (auto viewportContext = RetrieveViewportContext(m_viewportId))
|
||||
{
|
||||
return viewportContext->GetCameraTransform();
|
||||
}
|
||||
|
||||
return AZ::Transform::CreateIdentity();
|
||||
}
|
||||
void ModularCameraViewportContextImpl::SetCameraTransform(const AZ::Transform& transform)
|
||||
{
|
||||
if (auto viewportContext = RetrieveViewportContext(m_viewportId))
|
||||
{
|
||||
viewportContext->SetCameraTransform(transform);
|
||||
}
|
||||
}
|
||||
void ModularCameraViewportContextImpl::ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler)
|
||||
{
|
||||
if (auto viewportContext = RetrieveViewportContext(m_viewportId))
|
||||
{
|
||||
viewportContext->ConnectViewMatrixChangedHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void ModularViewportCameraController::SetCameraListBuilderCallback(const CameraListBuilder& builder)
|
||||
{
|
||||
m_cameraListBuilder = builder;
|
||||
@@ -73,6 +103,11 @@ namespace AtomToolsFramework
|
||||
m_cameraControllerPriorityBuilder = builder;
|
||||
}
|
||||
|
||||
void ModularViewportCameraController::SetCameraViewportContextBuilderCallback(const CameraViewportContextBuilder& builder)
|
||||
{
|
||||
m_cameraViewportContextBuilder = builder;
|
||||
}
|
||||
|
||||
void ModularViewportCameraController::SetupCameras(AzFramework::Cameras& cameras)
|
||||
{
|
||||
if (m_cameraListBuilder)
|
||||
@@ -97,6 +132,15 @@ namespace AtomToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void ModularViewportCameraController::SetupCameraControllerViewportContext(
|
||||
AZStd::unique_ptr<ModularCameraViewportContext>& cameraViewportContext)
|
||||
{
|
||||
if (m_cameraViewportContextBuilder)
|
||||
{
|
||||
m_cameraViewportContextBuilder(cameraViewportContext);
|
||||
}
|
||||
}
|
||||
|
||||
// what priority should the camera system respond to
|
||||
AzFramework::ViewportControllerPriority DefaultCameraControllerPriority(const AzFramework::CameraSystem& cameraSystem)
|
||||
{
|
||||
@@ -119,23 +163,20 @@ namespace AtomToolsFramework
|
||||
controller->SetupCameras(m_cameraSystem.m_cameras);
|
||||
controller->SetupCameraProperties(m_cameraProps);
|
||||
controller->SetupCameraControllerPriority(m_priorityFn);
|
||||
controller->SetupCameraControllerViewportContext(m_modularCameraViewportContext);
|
||||
|
||||
if (auto viewportContext = RetrieveViewportContext(GetViewportId()))
|
||||
auto handleCameraChange = [this](const AZ::Matrix4x4&)
|
||||
{
|
||||
auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&)
|
||||
// ignore these updates if the camera is being updated internally
|
||||
if (!m_updatingTransformInternally)
|
||||
{
|
||||
// ignore these updates if the camera is being updated internally
|
||||
if (!m_updatingTransformInternally)
|
||||
{
|
||||
UpdateCameraFromTransform(m_targetCamera, viewportContext->GetCameraTransform());
|
||||
m_camera = m_targetCamera;
|
||||
}
|
||||
};
|
||||
UpdateCameraFromTransform(m_targetCamera, m_modularCameraViewportContext->GetCameraTransform());
|
||||
m_camera = m_targetCamera;
|
||||
}
|
||||
};
|
||||
|
||||
m_cameraViewMatrixChangeHandler = AZ::RPI::ViewportContext::MatrixChangedEvent::Handler(handleCameraChange);
|
||||
|
||||
viewportContext->ConnectViewMatrixChangedHandler(m_cameraViewMatrixChangeHandler);
|
||||
}
|
||||
m_cameraViewMatrixChangeHandler = AZ::RPI::ViewportContext::MatrixChangedEvent::Handler(handleCameraChange);
|
||||
m_modularCameraViewportContext->ConnectViewMatrixChangedHandler(m_cameraViewMatrixChangeHandler);
|
||||
|
||||
AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
|
||||
ModularViewportCameraControllerRequestBus::Handler::BusConnect(viewportId);
|
||||
@@ -151,7 +192,11 @@ namespace AtomToolsFramework
|
||||
{
|
||||
if (event.m_priority == m_priorityFn(m_cameraSystem))
|
||||
{
|
||||
return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel));
|
||||
AzFramework::WindowSize windowSize;
|
||||
AzFramework::WindowRequestBus::EventResult(
|
||||
windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
|
||||
|
||||
return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -165,61 +210,58 @@ namespace AtomToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto viewportContext = RetrieveViewportContext(GetViewportId()))
|
||||
m_updatingTransformInternally = true;
|
||||
|
||||
if (m_cameraMode == CameraMode::Control)
|
||||
{
|
||||
m_updatingTransformInternally = true;
|
||||
m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count());
|
||||
m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, m_cameraProps, event.m_deltaTime.count());
|
||||
|
||||
if (m_cameraMode == CameraMode::Control)
|
||||
// if there has been an interpolation, only clear the look at point if it is no longer
|
||||
// centered in the view (the camera has looked away from it)
|
||||
if (m_lookAtAfterInterpolation.has_value())
|
||||
{
|
||||
m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count());
|
||||
m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, m_cameraProps, event.m_deltaTime.count());
|
||||
|
||||
// if there has been an interpolation, only clear the look at point if it is no longer
|
||||
// centered in the view (the camera has looked away from it)
|
||||
if (m_lookAtAfterInterpolation.has_value())
|
||||
if (const float lookDirection =
|
||||
(*m_lookAtAfterInterpolation - m_camera.Translation()).GetNormalized().Dot(m_camera.Transform().GetBasisY());
|
||||
!AZ::IsCloseMag(lookDirection, 1.0f, 0.001f))
|
||||
{
|
||||
if (const float lookDirection =
|
||||
(*m_lookAtAfterInterpolation - m_camera.Translation()).GetNormalized().Dot(m_camera.Transform().GetBasisY());
|
||||
!AZ::IsCloseMag(lookDirection, 1.0f, 0.001f))
|
||||
{
|
||||
m_lookAtAfterInterpolation = {};
|
||||
}
|
||||
m_lookAtAfterInterpolation = {};
|
||||
}
|
||||
|
||||
viewportContext->SetCameraTransform(m_camera.Transform());
|
||||
}
|
||||
else if (m_cameraMode == CameraMode::Animation)
|
||||
{
|
||||
const auto smootherStepFn = [](const float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
||||
};
|
||||
|
||||
const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation;
|
||||
|
||||
const float transitionTime = smootherStepFn(animationTime);
|
||||
const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionTime),
|
||||
transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionTime));
|
||||
|
||||
const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current));
|
||||
m_camera.m_pitch = eulerAngles.GetX();
|
||||
m_camera.m_yaw = eulerAngles.GetZ();
|
||||
m_camera.m_lookAt = current.GetTranslation();
|
||||
m_targetCamera = m_camera;
|
||||
|
||||
if (animationTime >= 1.0f)
|
||||
{
|
||||
m_cameraMode = CameraMode::Control;
|
||||
}
|
||||
|
||||
m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f);
|
||||
|
||||
viewportContext->SetCameraTransform(current);
|
||||
}
|
||||
|
||||
m_updatingTransformInternally = false;
|
||||
m_modularCameraViewportContext->SetCameraTransform(m_camera.Transform());
|
||||
}
|
||||
else if (m_cameraMode == CameraMode::Animation)
|
||||
{
|
||||
const auto smootherStepFn = [](const float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
||||
};
|
||||
|
||||
const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation;
|
||||
|
||||
const float transitionTime = smootherStepFn(animationTime);
|
||||
const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionTime),
|
||||
transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionTime));
|
||||
|
||||
const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current));
|
||||
m_camera.m_pitch = eulerAngles.GetX();
|
||||
m_camera.m_yaw = eulerAngles.GetZ();
|
||||
m_camera.m_lookAt = current.GetTranslation();
|
||||
m_targetCamera = m_camera;
|
||||
|
||||
if (animationTime >= 1.0f)
|
||||
{
|
||||
m_cameraMode = CameraMode::Control;
|
||||
}
|
||||
|
||||
m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f);
|
||||
|
||||
m_modularCameraViewportContext->SetCameraTransform(current);
|
||||
}
|
||||
|
||||
m_updatingTransformInternally = false;
|
||||
}
|
||||
|
||||
void ModularViewportCameraControllerInstance::DisplayViewport(
|
||||
|
||||
Reference in New Issue
Block a user