Fix camera drift issues (#4576)
* remove some unused code in RenderViewportWidget and make viewing devicePixelRatioF easier Signed-off-by: hultonha <hultonha@amazon.co.uk> * updates to how cursor positions are calculate to handle the viewport widget moving Signed-off-by: hultonha <hultonha@amazon.co.uk> * remove optional for previous position Signed-off-by: hultonha <hultonha@amazon.co.uk> * add test to capture error with moving the widget Signed-off-by: hultonha <hultonha@amazon.co.uk> * minor comment updates before publishing PR Signed-off-by: hultonha <hultonha@amazon.co.uk>
This commit is contained in:
@@ -69,6 +69,7 @@ namespace UnitTest
|
||||
|
||||
m_rootWidget = AZStd::make_unique<QWidget>();
|
||||
m_rootWidget->setFixedSize(WidgetSize);
|
||||
m_rootWidget->move(0, 0); // explicitly set the widget to be in the upper left corner
|
||||
|
||||
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
|
||||
m_controllerList->RegisterViewportContext(TestViewportId);
|
||||
@@ -344,4 +345,51 @@ namespace UnitTest
|
||||
// Clean-up
|
||||
HaltCollaborators();
|
||||
}
|
||||
|
||||
// test to verify deltas and cursor positions are handled correctly when the widget is moved
|
||||
TEST_F(ModularViewportCameraControllerFixture, CameraDoesNotStutterAfterWidgetIsMoved)
|
||||
{
|
||||
// Given
|
||||
PrepareCollaborators();
|
||||
SandboxEditor::SetCameraCaptureCursorForLook(true);
|
||||
|
||||
const float deltaTime = 1.0f / 60.0f;
|
||||
|
||||
// When
|
||||
// move cursor 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() });
|
||||
|
||||
// move camera right
|
||||
const auto mouseDelta = QPoint(200, 0);
|
||||
MousePressAndMove(m_rootWidget.get(), start, mouseDelta, Qt::MouseButton::RightButton);
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
|
||||
QTest::mouseRelease(m_rootWidget.get(), Qt::MouseButton::RightButton, Qt::NoModifier, start + mouseDelta);
|
||||
|
||||
// update the position of the widget
|
||||
const auto offset = QPoint(500, 500);
|
||||
m_rootWidget->move(offset);
|
||||
|
||||
// move cursor back to widget center
|
||||
MouseMove(m_rootWidget.get(), start, QPoint(0, 0));
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
|
||||
// move camera left
|
||||
MousePressAndMove(m_rootWidget.get(), start, -mouseDelta, Qt::MouseButton::RightButton);
|
||||
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
|
||||
|
||||
// Then
|
||||
// ensure the camera rotation has returned to the identity
|
||||
const AZ::Quaternion cameraRotation = m_cameraViewportContextView->GetCameraTransform().GetRotation();
|
||||
const auto eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromQuaternion(cameraRotation));
|
||||
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(eulerAngles.GetX(), FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(eulerAngles.GetZ(), FloatNear(0.0f, 0.001f));
|
||||
|
||||
// Clean-up
|
||||
HaltCollaborators();
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
+10
-10
@@ -261,10 +261,10 @@ namespace AzToolsFramework
|
||||
// ensures cursor positions are refreshed correctly with context menu focus changes)
|
||||
if (eventType == QEvent::FocusIn)
|
||||
{
|
||||
const auto widgetCursorPosition = m_sourceWidget->mapFromGlobal(QCursor::pos());
|
||||
if (m_sourceWidget->geometry().contains(widgetCursorPosition))
|
||||
const auto globalCursorPosition = QCursor::pos();
|
||||
if (m_sourceWidget->geometry().contains(globalCursorPosition))
|
||||
{
|
||||
HandleMouseMoveEvent(widgetCursorPosition);
|
||||
HandleMouseMoveEvent(globalCursorPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +290,7 @@ namespace AzToolsFramework
|
||||
else if (eventType == QEvent::Type::MouseMove)
|
||||
{
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
HandleMouseMoveEvent(mouseEvent->pos());
|
||||
HandleMouseMoveEvent(mouseEvent->globalPos());
|
||||
}
|
||||
// Map wheel events to the mouse Z movement channel.
|
||||
else if (eventType == QEvent::Type::Wheel)
|
||||
@@ -370,11 +370,12 @@ namespace AzToolsFramework
|
||||
return QPoint{ denormalizedX, denormalizedY };
|
||||
}
|
||||
|
||||
void QtEventToAzInputMapper::HandleMouseMoveEvent(const QPoint& cursorPosition)
|
||||
void QtEventToAzInputMapper::HandleMouseMoveEvent(const QPoint& globalCursorPosition)
|
||||
{
|
||||
const QPoint cursorDelta = cursorPosition - m_previousCursorPosition;
|
||||
const QPoint cursorDelta = globalCursorPosition - m_previousGlobalCursorPosition;
|
||||
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition = WidgetPositionToNormalizedPosition(cursorPosition);
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPosition =
|
||||
WidgetPositionToNormalizedPosition(m_sourceWidget->mapFromGlobal(globalCursorPosition));
|
||||
m_mouseDevice->m_cursorPositionData2D->m_normalizedPositionDelta = WidgetPositionToNormalizedPosition(cursorDelta);
|
||||
|
||||
ProcessPendingMouseEvents(cursorDelta);
|
||||
@@ -382,12 +383,11 @@ namespace AzToolsFramework
|
||||
if (m_capturingCursor)
|
||||
{
|
||||
// Reset our cursor position to the previous point
|
||||
const QPoint screenCursorPosition = m_sourceWidget->mapToGlobal(m_previousCursorPosition);
|
||||
AzQtComponents::SetCursorPos(screenCursorPosition);
|
||||
AzQtComponents::SetCursorPos(m_previousGlobalCursorPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_previousCursorPosition = cursorPosition;
|
||||
m_previousGlobalCursorPosition = globalCursorPosition;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace AzToolsFramework
|
||||
// Handle mouse click events.
|
||||
void HandleMouseButtonEvent(QMouseEvent* mouseEvent);
|
||||
// Handle mouse move events.
|
||||
void HandleMouseMoveEvent(const QPoint& cursorPosition);
|
||||
void HandleMouseMoveEvent(const QPoint& globalCursorPosition);
|
||||
// Handles key press / release events (or ShortcutOverride events for keys listed in m_highPriorityKeys).
|
||||
void HandleKeyEvent(QKeyEvent* keyEvent);
|
||||
// Handles mouse wheel events.
|
||||
@@ -156,8 +156,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;
|
||||
// Where the mouse cursor was at the last cursor event.
|
||||
QPoint m_previousGlobalCursorPosition;
|
||||
// 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.
|
||||
|
||||
+6
-9
@@ -90,7 +90,7 @@ namespace AtomToolsFramework
|
||||
//! Input processing is enabled by default.
|
||||
void SetInputProcessingEnabled(bool enabled);
|
||||
|
||||
// AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler ...
|
||||
// AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler overrides ...
|
||||
AzFramework::CameraState GetCameraState() override;
|
||||
AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override;
|
||||
AZStd::optional<AZ::Vector3> ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) override;
|
||||
@@ -98,12 +98,12 @@ namespace AtomToolsFramework
|
||||
const AzFramework::ScreenPoint& screenPosition) override;
|
||||
float DeviceScalingFactor() override;
|
||||
|
||||
// AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler ...
|
||||
// AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler overrides ...
|
||||
void BeginCursorCapture() override;
|
||||
void EndCursorCapture() override;
|
||||
bool IsMouseOver() const override;
|
||||
|
||||
// AzFramework::WindowRequestBus::Handler ...
|
||||
// AzFramework::WindowRequestBus::Handler overrides ...
|
||||
void SetWindowTitle(const AZStd::string& title) override;
|
||||
AzFramework::WindowSize GetClientAreaSize() const override;
|
||||
void ResizeClientArea(AzFramework::WindowSize clientAreaSize) override;
|
||||
@@ -116,18 +116,17 @@ namespace AtomToolsFramework
|
||||
uint32_t GetDisplayRefreshRate() const override;
|
||||
|
||||
protected:
|
||||
// AzFramework::InputChannelEventListener ...
|
||||
// AzFramework::InputChannelEventListener overrides ...
|
||||
bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
|
||||
|
||||
// AZ::TickBus::Handler ...
|
||||
// AZ::TickBus::Handler overrides ...
|
||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
||||
|
||||
// QWidget ...
|
||||
// QWidget overrides ...
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
bool event(QEvent* event) override;
|
||||
void enterEvent(QEvent* event) override;
|
||||
void leaveEvent(QEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
void SendWindowResizeEvent();
|
||||
@@ -143,8 +142,6 @@ namespace AtomToolsFramework
|
||||
AZ::RPI::AuxGeomDrawPtr m_auxGeom;
|
||||
// Tracks whether the cursor is currently over our viewport, used for mouse input event book-keeping.
|
||||
bool m_mouseOver = false;
|
||||
// The last recorded mouse position, in local viewport screen coordinates.
|
||||
QPointF m_mousePosition;
|
||||
// Captures the time between our render events to give controllers a time delta.
|
||||
QElapsedTimer m_renderTimer;
|
||||
// The time of the last recorded tick event from the system tick bus.
|
||||
|
||||
@@ -214,20 +214,17 @@ namespace AtomToolsFramework
|
||||
m_mouseOver = false;
|
||||
}
|
||||
|
||||
void RenderViewportWidget::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
m_mousePosition = event->localPos();
|
||||
}
|
||||
|
||||
void RenderViewportWidget::SendWindowResizeEvent()
|
||||
{
|
||||
// Scale the size by the DPI of the platform to
|
||||
// get the proper size in pixels.
|
||||
const auto pixelRatio = devicePixelRatioF();
|
||||
const QSize uiWindowSize = size();
|
||||
const QSize windowSize = uiWindowSize * devicePixelRatioF();
|
||||
const QSize windowSize = uiWindowSize * pixelRatio;
|
||||
|
||||
const AzFramework::NativeWindowHandle windowId = reinterpret_cast<AzFramework::NativeWindowHandle>(winId());
|
||||
AzFramework::WindowNotificationBus::Event(windowId, &AzFramework::WindowNotifications::OnWindowResized, windowSize.width(), windowSize.height());
|
||||
AzFramework::WindowNotificationBus::Event(
|
||||
windowId, &AzFramework::WindowNotifications::OnWindowResized, windowSize.width(), windowSize.height());
|
||||
}
|
||||
|
||||
AZ::Name RenderViewportWidget::GetCurrentContextName() const
|
||||
|
||||
Reference in New Issue
Block a user