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
)
@@ -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
@@ -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.
@@ -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;
}
@@ -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
@@ -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(