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>
monroegm-disable-blank-issue-2
hultonha 4 years ago committed by GitHub
parent 3b89f7e1cd
commit 5c859cb134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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.

@ -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

Loading…
Cancel
Save