/* * 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 * */ // Description : implementation filefov #include "EditorDefs.h" #include "RenderViewport.h" // Qt #include #include #include #include #include // AzCore #include #include #include #include #include #include #include // AzFramework #include #include #include #if defined(AZ_PLATFORM_WINDOWS) # include #endif // defined(AZ_PLATFORM_WINDOWS) #include // for AzFramework::InputDeviceMouse // AzQtComponents #include // AzToolsFramework #include #include #include #include #include #include // CryCommon #include // AzFramework #include // Editor #include "Util/fastlib.h" #include "CryEditDoc.h" #include "GameEngine.h" #include "ViewManager.h" #include "Objects/DisplayContext.h" #include "DisplaySettings.h" #include "Include/IObjectManager.h" #include "Include/IDisplayViewport.h" #include "Objects/ObjectManager.h" #include "ProcessInfo.h" #include "IPostEffectGroup.h" #include "EditorPreferencesPageGeneral.h" #include "ViewPane.h" #include "CustomResolutionDlg.h" #include "AnimationContext.h" #include "Objects/SelectionGroup.h" #include "Core/QtEditorApplication.h" // ComponentEntityEditorPlugin #include // LmbrCentral #include #include #include #include #include AZ_CVAR( bool, ed_visibility_use, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enable/disable using the new IVisibilitySystem for Entity visibility determination"); CRenderViewport* CRenderViewport::m_pPrimaryViewport = nullptr; #if AZ_TRAIT_OS_PLATFORM_APPLE void StopFixedCursorMode(); void StartFixedCursorMode(QObject *viewport); #endif #define MAX_ORBIT_DISTANCE (2000.0f) #define RENDER_MESH_TEST_DISTANCE (0.2f) #define CURSOR_FONT_HEIGHT 8.0f #define FORWARD_DIRECTION Vec3(0, 1, 0) static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded."; class EditorEntityNotifications : public AzToolsFramework::EditorEntityContextNotificationBus::Handler , public AzToolsFramework::EditorContextMenuBus::Handler { public: EditorEntityNotifications(CRenderViewport& renderViewport) : m_renderViewport(renderViewport) { AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect(); AzToolsFramework::EditorContextMenuBus::Handler::BusConnect(); } ~EditorEntityNotifications() override { AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect(); AzToolsFramework::EditorContextMenuBus::Handler::BusDisconnect(); } // AzToolsFramework::EditorEntityContextNotificationBus void OnStartPlayInEditor() override { m_renderViewport.OnStartPlayInEditor(); } void OnStopPlayInEditor() override { m_renderViewport.OnStopPlayInEditor(); } // AzToolsFramework::EditorContextMenu::Bus void PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags) override { m_renderViewport.PopulateEditorGlobalContextMenu(menu, point, flags); } private: CRenderViewport& m_renderViewport; }; struct CRenderViewport::SScopedCurrentContext { const CRenderViewport* m_viewport; CRenderViewport::SPreviousContext m_previousContext; explicit SScopedCurrentContext(const CRenderViewport* viewport) : m_viewport(viewport) { m_previousContext = viewport->SetCurrentContext(); // During normal updates of RenderViewport the value of m_cameraSetForWidgetRenderingCount is expected to be 0. // This is to guarantee no loss in performance by tracking unnecessary calls to SetCurrentContext/RestorePreviousContext. // If some code makes additional calls to Pre/PostWidgetRendering then the assert will be triggered because // m_cameraSetForWidgetRenderingCount will be greater than 0. // There is a legitimate case where the counter can be greater than 0. This is when QtViewport is processing mouse callbacks. // QtViewport::MouseCallback() is surrounded by Pre/PostWidgetRendering and the m_processingMouseCallbacksCounter // tracks this specific case. If an update of a RenderViewport happens while processing the mouse callback, // for example when showing a QMessageBox, then both counters must match. AZ_Assert(viewport->m_cameraSetForWidgetRenderingCount == viewport->m_processingMouseCallbacksCounter, "SScopedCurrentContext constructor was called while viewport widget context is active " "- this is unnecessary"); } ~SScopedCurrentContext() { m_viewport->RestorePreviousContext(m_previousContext); } }; ////////////////////////////////////////////////////////////////////////// // CRenderViewport ////////////////////////////////////////////////////////////////////////// CRenderViewport::CRenderViewport(const QString& name, QWidget* parent) : QtViewport(parent) , m_Camera(GetIEditor()->GetSystem()->GetViewCamera()) , m_camFOV(gSettings.viewports.fDefaultFov) , m_defaultViewName(name) { // need this to be set in order to allow for language switching on Windows setAttribute(Qt::WA_InputMethodEnabled); LockCameraMovement(true); CRenderViewport::SetViewTM(m_Camera.GetMatrix()); m_defaultViewTM.SetIdentity(); if (GetIEditor()->GetViewManager()->GetSelectedViewport() == nullptr) { GetIEditor()->GetViewManager()->SelectViewport(this); } GetIEditor()->RegisterNotifyListener(this); m_displayContext.pIconManager = GetIEditor()->GetIconManager(); GetIEditor()->GetUndoManager()->AddListener(this); m_PhysicalLocation.SetIdentity(); // The renderer requires something, so don't allow us to shrink to absolutely nothing // This won't in fact stop the viewport from being shrunk, when it's the centralWidget for // the MainWindow, but it will stop the viewport from getting resize events // once it's smaller than that, which from the renderer's perspective works out // to be the same thing. setMinimumSize(50, 50); OnCreate(); setFocusPolicy(Qt::StrongFocus); Camera::EditorCameraRequestBus::Handler::BusConnect(); m_editorEntityNotifications = AZStd::make_unique(*this); m_manipulatorManager = GetIEditor()->GetViewManager()->GetManipulatorManager(); if (!m_pPrimaryViewport) { m_pPrimaryViewport = this; } m_hwnd = renderOverlayHWND(); } ////////////////////////////////////////////////////////////////////////// CRenderViewport::~CRenderViewport() { AzFramework::WindowNotificationBus::Event(m_hwnd, &AzFramework::WindowNotificationBus::Handler::OnWindowClosed); if (m_pPrimaryViewport == this) { m_pPrimaryViewport = nullptr; } AzFramework::WindowRequestBus::Handler::BusDisconnect(); DisconnectViewportInteractionRequestBus(); m_editorEntityNotifications.reset(); Camera::EditorCameraRequestBus::Handler::BusDisconnect(); OnDestroy(); GetIEditor()->GetUndoManager()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); } ////////////////////////////////////////////////////////////////////////// // CRenderViewport message handlers ////////////////////////////////////////////////////////////////////////// int CRenderViewport::OnCreate() { CreateRenderContext(); return 0; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::resizeEvent(QResizeEvent* event) { PushDisableRendering(); QtViewport::resizeEvent(event); PopDisableRendering(); const QRect rcWindow = rect().translated(mapToGlobal(QPoint())); gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_MOVE, rcWindow.left(), rcWindow.top()); m_rcClient = rect(); m_rcClient.setBottomRight(WidgetToViewport(m_rcClient.bottomRight())); gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RESIZE, width(), height()); // We queue the window resize event because the render overlay may be hidden. // If the render overlay is not visible, the native window that is backing it will // also be hidden, and it will not resize until it becomes visible. m_windowResizedEvent = true; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::paintEvent([[maybe_unused]] QPaintEvent* event) { // Do not call CViewport::OnPaint() for painting messages // FIXME: paintEvent() isn't the best place for such logic. Should listen to proper eNotify events and to the stuff there instead. (Repeats for other view port classes too). CGameEngine* ge = GetIEditor()->GetGameEngine(); if ((ge && ge->IsLevelLoaded()) || (GetType() != ET_ViewportCamera)) { setRenderOverlayVisible(true); m_isOnPaint = true; Update(); m_isOnPaint = false; } else { setRenderOverlayVisible(false); QPainter painter(this); // device context for painting // draw gradient background const QRect rc = rect(); QLinearGradient gradient(rc.topLeft(), rc.bottomLeft()); gradient.setColorAt(0, QColor(80, 80, 80)); gradient.setColorAt(1, QColor(200, 200, 200)); painter.fillRect(rc, gradient); // if we have some level loaded/loading/new // we draw a text if (!GetIEditor()->GetLevelFolder().isEmpty()) { const int kFontSize = 200; const char* kFontName = "Arial"; const QColor kTextColor(255, 255, 255); const QColor kTextShadowColor(0, 0, 0); const QFont font(kFontName, kFontSize / 10.0); painter.setFont(font); QString friendlyName = QFileInfo(GetIEditor()->GetLevelName()).fileName(); const QString strMsg = tr("Preparing level %1...").arg(friendlyName); // draw text shadow painter.setPen(kTextShadowColor); painter.drawText(rc, Qt::AlignCenter, strMsg); painter.setPen(kTextColor); // offset rect for normal text painter.drawText(rc.translated(-1, -1), Qt::AlignCenter, strMsg); } } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::mousePressEvent(QMouseEvent* event) { // There's a bug caused by having a mix of MFC and Qt where if the render viewport // had focus and then an MFC widget gets focus, Qt internally still thinks // that the widget that had focus before (the render viewport) has it now. // Because of this, Qt won't set the window that the viewport is in as the // focused widget, and the render viewport won't get keyboard input. // Forcing the window to activate should allow the window to take focus // and then the call to setFocus() will give it focus. // All so that the ::keyPressEvent() gets called. ActivateWindowAndSetFocus(); GetIEditor()->GetViewManager()->SelectViewport(this); QtViewport::mousePressEvent(event); } AzToolsFramework::ViewportInteraction::MousePick CRenderViewport::BuildMousePickInternal(const QPoint& point) const { using namespace AzToolsFramework::ViewportInteraction; MousePick mousePick; Vec3 from, dir; ViewToWorldRay(point, from, dir); mousePick.m_rayOrigin = LYVec3ToAZVec3(from); mousePick.m_rayDirection = LYVec3ToAZVec3(dir); mousePick.m_screenCoordinates = AzFramework::ScreenPoint(point.x(), point.y()); return mousePick; } AzToolsFramework::ViewportInteraction::MousePick CRenderViewport::BuildMousePick(const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; PreWidgetRendering(); const MousePick mousePick = BuildMousePickInternal(point); PostWidgetRendering(); return mousePick; } AzToolsFramework::ViewportInteraction::MouseInteraction CRenderViewport::BuildMouseInteractionInternal( const AzToolsFramework::ViewportInteraction::MouseButtons buttons, const AzToolsFramework::ViewportInteraction::KeyboardModifiers modifiers, const AzToolsFramework::ViewportInteraction::MousePick& mousePick) const { using namespace AzToolsFramework::ViewportInteraction; MouseInteraction mouse; mouse.m_interactionId.m_cameraId = m_viewEntityId; mouse.m_interactionId.m_viewportId = GetViewportId(); mouse.m_mouseButtons = buttons; mouse.m_mousePick = mousePick; mouse.m_keyboardModifiers = modifiers; return mouse; } AzToolsFramework::ViewportInteraction::MouseInteraction CRenderViewport::BuildMouseInteraction( const Qt::MouseButtons buttons, const Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; return BuildMouseInteractionInternal( BuildMouseButtons(buttons), BuildKeyboardModifiers(modifiers), BuildMousePick(WidgetToViewport(point))); } namespace RenderViewportUtil { static bool JustAltHeld(const Qt::KeyboardModifiers modifiers) { return (modifiers & Qt::ShiftModifier) == 0 && (modifiers & Qt::ControlModifier) == 0 && (modifiers & Qt::AltModifier) != 0; } static bool NoModifiersHeld(const Qt::KeyboardModifiers modifiers) { return (modifiers & Qt::ShiftModifier) == 0 && (modifiers & Qt::ControlModifier) == 0 && (modifiers & Qt::AltModifier) == 0; } static bool AllowDolly(const Qt::KeyboardModifiers modifiers) { return JustAltHeld(modifiers); } static bool AllowOrbit(const Qt::KeyboardModifiers modifiers) { return JustAltHeld(modifiers); } static bool AllowPan(const Qt::KeyboardModifiers modifiers) { // begin pan with alt (inverted movement) or no modifiers return JustAltHeld(modifiers) || NoModifiersHeld(modifiers); } static bool InvertPan(const Qt::KeyboardModifiers modifiers) { return JustAltHeld(modifiers); } } // namespace RenderViewportUtil ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnLButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } if (!m_renderer) { return; } // Force the visible object cache to be updated - this is to ensure that // selection will work properly even if helpers are not being displayed, // in which case the cache is not updated every frame. if (m_displayContext.settings && !m_displayContext.settings->IsDisplayHelpers()) { GetIEditor()->GetObjectManager()->ForceUpdateVisibleObjectCache(m_displayContext); } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Left), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); bool manipulatorInteraction = false; EditorInteractionSystemViewportSelectionRequestBus::EventResult( manipulatorInteraction, AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleMouseManipulatorInteraction, MouseInteractionEvent(mouseInteraction, MouseEvent::Down)); if (!manipulatorInteraction) { if (RenderViewportUtil::AllowOrbit(modifiers)) { m_bInOrbitMode = true; m_orbitTarget = GetViewTM().GetTranslation() + GetViewTM().TransformVector(FORWARD_DIRECTION) * m_orbitDistance; // mouse buttons are treated as keys as well if (m_pressedKeyState == KeyPressedState::AllUp) { m_pressedKeyState = KeyPressedState::PressedThisFrame; } m_mousePos = scaledPoint; m_prevMousePos = scaledPoint; HideCursor(); CaptureMouse(); // no further handling of left mouse button down return; } EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleMouseViewportInteraction, MouseInteractionEvent(mouseInteraction, MouseEvent::Down)); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnLButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } // Convert point to position on terrain. if (!m_renderer) { return; } // Update viewports after done with actions. GetIEditor()->UpdateViews(eUpdateObjects); const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Left), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); if (m_bInOrbitMode) { m_bInOrbitMode = false; ReleaseMouse(); ShowCursor(); } AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Up)); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnLButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Left), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::DoubleClick)); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } SetFocus(); const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Right), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Down)); if (RenderViewportUtil::AllowDolly(modifiers)) { m_bInZoomMode = true; } else { m_bInRotateMode = true; } // mouse buttons are treated as keys as well if (m_pressedKeyState == KeyPressedState::AllUp) { m_pressedKeyState = KeyPressedState::PressedThisFrame; } m_mousePos = scaledPoint; m_prevMousePos = m_mousePos; HideCursor(); // we can't capture the mouse here, or it will stop the popup menu // when the mouse is released. } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnRButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Right), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Up)); m_bInRotateMode = false; m_bInZoomMode = false; ReleaseMouse(); if (!m_bInMoveMode) { ShowCursor(); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Middle), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); if (RenderViewportUtil::AllowPan(modifiers)) { m_bInMoveMode = true; // mouse buttons are treated as keys as well if (m_pressedKeyState == KeyPressedState::AllUp) { m_pressedKeyState = KeyPressedState::PressedThisFrame; } m_mousePos = scaledPoint; m_prevMousePos = scaledPoint; HideCursor(); CaptureMouse(); } AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Down)); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnMButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); UpdateCurrentMousePos(scaledPoint); const auto tryRestoreMouse = [this] { // if we are currently looking (rotateMode) or dollying (zoomMode) // do not show the cursor on mouse up as rmb is still held if (!m_bInZoomMode && !m_bInRotateMode) { ReleaseMouse(); ShowCursor(); } }; if (m_bInMoveMode) { m_bInMoveMode = false; tryRestoreMouse(); } const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::Middle), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Up)); } void CRenderViewport::OnMouseMove(Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( BuildMouseButtons(buttons), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, MouseEvent::Move)); } void CRenderViewport::InjectFakeMouseMove(int deltaX, int deltaY, Qt::MouseButtons buttons) { // this is required, otherwise the user will see the context menu OnMouseMove(Qt::NoModifier, buttons, QCursor::pos() + QPoint(deltaX, deltaY)); // we simply move the prev mouse position, so the change will be picked up // by the next ProcessMouse call m_prevMousePos -= QPoint(deltaX, deltaY); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ProcessMouse() { if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto point = WidgetToViewport(mapFromGlobal(QCursor::pos())); if (point == m_prevMousePos) { return; } // specifically for the right mouse button click, which triggers rotate or zoom, // we can't capture the mouse until the user has moved the mouse, otherwise the // right click context menu won't popup if (!m_mouseCaptured && (m_bInZoomMode || m_bInRotateMode)) { if ((point - m_mousePos).manhattanLength() > QApplication::startDragDistance()) { CaptureMouse(); } } float speedScale = GetCameraMoveSpeed(); if (CheckVirtualKey(Qt::Key_Control)) { speedScale *= gSettings.cameraFastMoveSpeed; } if (m_PlayerControl) { if (m_bInRotateMode) { f32 MousedeltaX = (m_mousePos.x() - point.x()); f32 MousedeltaY = (m_mousePos.y() - point.y()); m_relCameraRotZ += MousedeltaX; if (GetCameraInvertYRotation()) { MousedeltaY = -MousedeltaY; } m_relCameraRotZ += MousedeltaX; m_relCameraRotX += MousedeltaY; ResetCursor(); } } else if ((m_bInRotateMode && m_bInMoveMode) || m_bInZoomMode) { // Zoom. Matrix34 m = GetViewTM(); Vec3 ydir = m.GetColumn1().GetNormalized(); Vec3 pos = m.GetTranslation(); const float posDelta = 0.2f * (m_prevMousePos.y() - point.y()) * speedScale; pos = pos - ydir * posDelta; m_orbitDistance = m_orbitDistance + posDelta; m_orbitDistance = fabs(m_orbitDistance); m.SetTranslation(pos); SetViewTM(m); ResetCursor(); } else if (m_bInRotateMode) { Ang3 angles(-point.y() + m_prevMousePos.y(), 0, -point.x() + m_prevMousePos.x()); angles = angles * 0.002f * GetCameraRotateSpeed(); if (GetCameraInvertYRotation()) { angles.x = -angles.x; } Matrix34 camtm = GetViewTM(); Ang3 ypr = CCamera::CreateAnglesYPR(Matrix33(camtm)); ypr.x += angles.z; ypr.y += angles.x; ypr.y = CLAMP(ypr.y, -1.5f, 1.5f); // to keep rotation in reasonable range // In the recording mode of a custom camera, the z rotation is allowed. if (GetCameraObject() == nullptr || (!GetIEditor()->GetAnimation()->IsRecordMode() && !IsCameraObjectMove())) { ypr.z = 0; // to have camera always upward } camtm = Matrix34(CCamera::CreateOrientationYPR(ypr), camtm.GetTranslation()); SetViewTM(camtm); ResetCursor(); } else if (m_bInMoveMode) { // Slide. Matrix34 m = GetViewTM(); Vec3 xdir = m.GetColumn0().GetNormalized(); Vec3 zdir = m.GetColumn2().GetNormalized(); const auto modifiers = QGuiApplication::queryKeyboardModifiers(); if (RenderViewportUtil::InvertPan(modifiers)) { xdir = -xdir; zdir = -zdir; } Vec3 pos = m.GetTranslation(); pos += 0.1f * xdir * (point.x() - m_prevMousePos.x()) * speedScale + 0.1f * zdir * (m_prevMousePos.y() - point.y()) * speedScale; m.SetTranslation(pos); SetViewTM(m, true); ResetCursor(); } else if (m_bInOrbitMode) { Ang3 angles(-point.y() + m_prevMousePos.y(), 0, -point.x() + m_prevMousePos.x()); angles = angles * 0.002f * GetCameraRotateSpeed(); if (GetCameraInvertPan()) { angles.z = -angles.z; } Ang3 ypr = CCamera::CreateAnglesYPR(Matrix33(GetViewTM())); ypr.x += angles.z; ypr.y = CLAMP(ypr.y, -1.5f, 1.5f); // to keep rotation in reasonable range ypr.y += angles.x; Matrix33 rotateTM = CCamera::CreateOrientationYPR(ypr); Vec3 src = GetViewTM().GetTranslation(); Vec3 trg = m_orbitTarget; float fCameraRadius = (trg - src).GetLength(); // Calc new source. src = trg - rotateTM * Vec3(0, 1, 0) * fCameraRadius; Matrix34 camTM = rotateTM; camTM.SetTranslation(src); SetViewTM(camTM); ResetCursor(); } } void CRenderViewport::ResetCursor() { #ifdef AZ_PLATFORM_WINDOWS if (!gSettings.stylusMode) { const QPoint point = mapToGlobal(ViewportToWidget(m_prevMousePos)); AzQtComponents::SetCursorPos(point); } #endif // Recalculate the prev mouse pos even if we just reset to it to avoid compounding floating point math issues with DPI scaling m_prevMousePos = WidgetToViewport(mapFromGlobal(QCursor::pos())); } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::event(QEvent* event) { switch (event->type()) { case QEvent::WindowActivate: GetIEditor()->GetViewManager()->SelectViewport(this); // also kill the keys; if we alt-tab back to the viewport, or come back from the debugger, it's done (and there's no guarantee we'll get the keyrelease event anyways) m_keyDown.clear(); break; case QEvent::Shortcut: // a shortcut should immediately clear us, otherwise the release event never gets sent m_keyDown.clear(); break; case QEvent::ShortcutOverride: { // since we respond to the following things, let Qt know so that shortcuts don't override us bool respondsToEvent = false; auto keyEvent = static_cast(event); bool manipulatorInteracting = false; AzToolsFramework::ManipulatorManagerRequestBus::EventResult( manipulatorInteracting, AzToolsFramework::g_mainManipulatorManagerId, &AzToolsFramework::ManipulatorManagerRequestBus::Events::Interacting); // If a manipulator is active, stop all shortcuts from working, except for the escape key, which cancels in some cases if ((keyEvent->key() != Qt::Key_Escape) && manipulatorInteracting) { respondsToEvent = true; } else { // In game mode we never want to be overridden by shortcuts if (GetIEditor()->IsInGameMode() && GetType() == ET_ViewportCamera) { respondsToEvent = true; } else { if (!(keyEvent->modifiers() & Qt::ControlModifier)) { switch (keyEvent->key()) { case Qt::Key_Up: case Qt::Key_W: case Qt::Key_Down: case Qt::Key_S: case Qt::Key_Left: case Qt::Key_A: case Qt::Key_Right: case Qt::Key_D: respondsToEvent = true; break; default: break; } } } } if (respondsToEvent) { event->accept(); return true; } // because we're doing keyboard grabs, we need to detect // when a shortcut matched so that we can track the buttons involved // in the shortcut, since the key released event won't be generated in that case ProcessKeyRelease(keyEvent); } break; default: // do nothing break; } return QtViewport::event(event); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ResetContent() { QtViewport::ResetContent(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::UpdateContent(int flags) { QtViewport::UpdateContent(flags); if (flags & eUpdateObjects) { m_bUpdateViewport = true; } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::Update() { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); if (Editor::EditorQtApplication::instance()->isMovingOrResizing()) { return; } if (!m_renderer || m_rcClient.isEmpty() || GetIEditor()->IsInMatEditMode()) { return; } if (!isVisible()) { return; } // Only send the resize event if the render overlay is visible. This is to make sure // the native window has resized. if (m_windowResizedEvent && isRenderOverlayVisible()) { AzFramework::WindowNotificationBus::Event(renderOverlayHWND(), &AzFramework::WindowNotificationBus::Handler::OnWindowResized, m_rcClient.width(), m_rcClient.height()); m_windowResizedEvent = false; } // Don't wait for changes to update the focused viewport. if (CheckRespondToInput()) { m_bUpdateViewport = true; } // While Renderer doesn't support fast rendering of the scene to more then 1 viewport // render only focused viewport if more then 1 are opened and always update is off. if (!m_isOnPaint && m_viewManager->GetNumberOfGameViewports() > 1 && GetType() == ET_ViewportCamera) { if (m_pPrimaryViewport != this) { if (CheckRespondToInput()) // If this is the focused window, set primary viewport. { m_pPrimaryViewport = this; } else if (!m_bUpdateViewport) // Skip this viewport. { return; } } } if (CheckRespondToInput()) { ProcessMouse(); ProcessKeys(); } const bool isGameMode = GetIEditor()->IsInGameMode(); const bool isSimulationMode = GetIEditor()->GetGameEngine()->GetSimulationMode(); // Allow debug visualization in both 'game' (Ctrl-G) and 'simulation' (Ctrl-P) modes if (isGameMode || isSimulationMode) { if (!IsRenderingDisabled()) { // Disable rendering to avoid recursion into Update() PushDisableRendering(); // draw debug visualizations { const AzFramework::DisplayContextRequestGuard displayContextGuard(m_displayContext); const AZ::u32 prevState = m_displayContext.GetState(); m_displayContext.SetState( e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; AzFramework::DebugDisplayRequestBus::Bind( debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus."); AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); AzFramework::EntityDebugDisplayEventBus::Broadcast( &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport, AzFramework::ViewportInfo{ GetViewportId() }, *debugDisplay); m_displayContext.SetState(prevState); } QtViewport::Update(); PopDisableRendering(); } // Game mode rendering is handled by CryAction if (isGameMode) { return; } } // Prevents rendering recursion due to recursive Paint messages. if (IsRenderingDisabled()) { return; } PushDisableRendering(); m_viewTM = m_Camera.GetMatrix(); // synchronize. // Render if (!m_bRenderContextCreated) { if (!CreateRenderContext()) { return; } } if (ed_visibility_use) { auto start = std::chrono::steady_clock::now(); m_entityVisibilityQuery.UpdateVisibility(GetCameraState()); } { SScopedCurrentContext context(this); m_renderer->SetClearColor(Vec3(0.4f, 0.4f, 0.4f)); InitDisplayContext(); OnRender(); ProcessRenderLisneters(m_displayContext); m_displayContext.Flush2D(); m_renderer->SwitchToNativeResolutionBackbuffer(); // 3D engine stats CCamera CurCamera = gEnv->pSystem->GetViewCamera(); gEnv->pSystem->SetViewCamera(m_Camera); // Post Render Callback { PostRenderers::iterator itr = m_postRenderers.begin(); PostRenderers::iterator end = m_postRenderers.end(); for (; itr != end; ++itr) { (*itr)->OnPostRender(); } } gEnv->pSystem->SetViewCamera(CurCamera); } QtViewport::Update(); PopDisableRendering(); m_bUpdateViewport = false; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetViewEntity(const AZ::EntityId& viewEntityId, bool lockCameraMovement) { // if they've picked the same camera, then that means they want to toggle if (viewEntityId.IsValid() && viewEntityId != m_viewEntityId) { LockCameraMovement(lockCameraMovement); m_viewEntityId = viewEntityId; AZStd::string entityName; AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, viewEntityId); SetName(QString("Camera entity: %1").arg(entityName.c_str())); } else { SetDefaultCamera(); } PostCameraSet(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ResetToViewSourceType(const ViewSourceType& viewSourceType) { LockCameraMovement(true); m_pCameraFOVVariable = nullptr; m_viewEntityId.SetInvalid(); m_cameraObjectId = GUID_NULL; m_viewSourceType = viewSourceType; SetViewTM(GetViewTM()); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::PostCameraSet() { if (m_viewPane) { m_viewPane->OnFOVChanged(GetFOV()); } GetIEditor()->Notify(eNotify_CameraChanged); QScopedValueRollback rb(m_ignoreSetViewFromEntityPerspective, true); Camera::EditorCameraNotificationBus::Broadcast( &Camera::EditorCameraNotificationBus::Events::OnViewportViewEntityChanged, m_viewEntityId); } ////////////////////////////////////////////////////////////////////////// CBaseObject* CRenderViewport::GetCameraObject() const { CBaseObject* pCameraObject = nullptr; if (m_viewSourceType == ViewSourceType::SequenceCamera) { m_cameraObjectId = GetViewManager()->GetCameraObjectId(); } if (m_cameraObjectId != GUID_NULL) { // Find camera object from id. pCameraObject = GetIEditor()->GetObjectManager()->FindObject(m_cameraObjectId); } else if (m_viewSourceType == ViewSourceType::CameraComponent || m_viewSourceType == ViewSourceType::AZ_Entity) { AzToolsFramework::ComponentEntityEditorRequestBus::EventResult( pCameraObject, m_viewEntityId, &AzToolsFramework::ComponentEntityEditorRequests::GetSandboxObject); } return pCameraObject; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnEditorNotifyEvent(EEditorNotifyEvent event) { switch (event) { case eNotify_OnBeginGameMode: { if (GetIEditor()->GetViewManager()->GetGameViewport() == this) { m_preGameModeViewTM = GetViewTM(); // this should only occur for the main viewport and no others. ShowCursor(); // If the user has selected game mode, enable outputting to any attached HMD and properly size the context // to the resolution specified by the VR device. if (gSettings.bEnableGameModeVR) { const AZ::VR::HMDDeviceInfo* deviceInfo = nullptr; EBUS_EVENT_RESULT(deviceInfo, AZ::VR::HMDDeviceRequestBus, GetDeviceInfo); AZ_Warning("Render Viewport", deviceInfo, "No VR device detected"); if (deviceInfo) { m_previousContext = SetCurrentContext(deviceInfo->renderWidth, deviceInfo->renderHeight); if (m_renderer->GetIStereoRenderer()) { m_renderer->GetIStereoRenderer()->OnResolutionChanged(); } SetActiveWindow(); SetFocus(); SetSelected(true); } } else { m_previousContext = SetCurrentContext(); } SetCurrentCursor(STD_CURSOR_GAME); } } break; case eNotify_OnEndGameMode: if (GetIEditor()->GetViewManager()->GetGameViewport() == this) { SetCurrentCursor(STD_CURSOR_DEFAULT); if (m_renderer->GetCurrentContextHWND() != renderOverlayHWND()) { // if this warning triggers it means that someone else (ie, some other part of the code) // called SetCurrentContext(...) on the renderer, probably did some rendering, but then either // failed to set the context back when done, or set it back to the wrong one. CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "RenderViewport render context was not correctly restored by someone else."); } RestorePreviousContext(m_previousContext); m_bInRotateMode = false; m_bInMoveMode = false; m_bInOrbitMode = false; m_bInZoomMode = false; RestoreViewportAfterGameMode(); } break; case eNotify_OnCloseScene: SetDefaultCamera(); break; case eNotify_OnBeginNewScene: PushDisableRendering(); break; case eNotify_OnEndNewScene: PopDisableRendering(); { // Default this to the size of default terrain in case there is no listener on the buss AZ::Aabb terrainAabb = AZ::Aabb::CreateFromMinMaxValues(0, 0, 32, 1024, 1024, 32); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); float sx = terrainAabb.GetXExtent(); float sy = terrainAabb.GetYExtent(); Matrix34 viewTM; viewTM.SetIdentity(); // Initial camera will be at middle of the map at the height of 2 // meters above the terrain (default terrain height is 32) viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f)); SetViewTM(viewTM); } break; case eNotify_OnBeginTerrainCreate: PushDisableRendering(); break; case eNotify_OnEndTerrainCreate: PopDisableRendering(); { // Default this to the size of default terrain in case there is no listener on the buss AZ::Aabb terrainAabb = AZ::Aabb::CreateFromMinMaxValues(0, 0, 32, 1024, 1024, 32); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); float sx = terrainAabb.GetXExtent(); float sy = terrainAabb.GetYExtent(); Matrix34 viewTM; viewTM.SetIdentity(); // Initial camera will be at middle of the map at the height of 2 // meters above the terrain (default terrain height is 32) viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f)); SetViewTM(viewTM); } break; case eNotify_OnBeginLayerExport: case eNotify_OnBeginSceneSave: PushDisableRendering(); break; case eNotify_OnEndLayerExport: case eNotify_OnEndSceneSave: PopDisableRendering(); break; case eNotify_OnBeginLoad: // disables viewport input when starting to load an existing level case eNotify_OnBeginCreate: // disables viewport input when starting to create a new level m_freezeViewportInput = true; break; case eNotify_OnEndLoad: // enables viewport input when finished loading an existing level case eNotify_OnEndCreate: // enables viewport input when finished creating a new level m_freezeViewportInput = false; break; } } ////////////////////////////////////////////////////////////////////////// namespace { inline Vec3 NegY(const Vec3& v, float y) { return Vec3(v.x, y - v.y, v.z); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnRender() { if (m_rcClient.isEmpty() || m_renderer->GetRenderType() == eRT_Null) // Null is crashing in CryEngine on macOS { // Even in null rendering, update the view camera. // This is necessary so that automated editor tests using the null renderer to test systems like dynamic vegetation // are still able to manipulate the current logical camera position, even if nothing is rendered. GetIEditor()->GetSystem()->SetViewCamera(m_Camera); return; } FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); float fNearZ = GetIEditor()->GetConsoleVar("cl_DefaultNearPlane"); float fFarZ = m_Camera.GetFarPlane(); CBaseObject* cameraObject = GetCameraObject(); if (cameraObject) { AZ::Matrix3x3 lookThroughEntityCorrection = AZ::Matrix3x3::CreateIdentity(); if (m_viewEntityId.IsValid()) { Camera::CameraRequestBus::EventResult(fNearZ, m_viewEntityId, &Camera::CameraComponentRequests::GetNearClipDistance); Camera::CameraRequestBus::EventResult(fFarZ, m_viewEntityId, &Camera::CameraComponentRequests::GetFarClipDistance); LmbrCentral::EditorCameraCorrectionRequestBus::EventResult( lookThroughEntityCorrection, m_viewEntityId, &LmbrCentral::EditorCameraCorrectionRequests::GetTransformCorrection); } m_viewTM = cameraObject->GetWorldTM() * AZMatrix3x3ToLYMatrix3x3(lookThroughEntityCorrection); m_viewTM.OrthonormalizeFast(); m_Camera.SetMatrix(m_viewTM); int w = m_rcClient.width(); int h = m_rcClient.height(); m_Camera.SetFrustum(w, h, GetFOV(), fNearZ, fFarZ); } else if (m_viewEntityId.IsValid()) { Camera::CameraRequestBus::EventResult(fNearZ, m_viewEntityId, &Camera::CameraComponentRequests::GetNearClipDistance); Camera::CameraRequestBus::EventResult(fFarZ, m_viewEntityId, &Camera::CameraComponentRequests::GetFarClipDistance); int w = m_rcClient.width(); int h = m_rcClient.height(); m_Camera.SetFrustum(w, h, GetFOV(), fNearZ, fFarZ); } else { // Normal camera. m_cameraObjectId = GUID_NULL; int w = m_rcClient.width(); int h = m_rcClient.height(); float fov = gSettings.viewports.fDefaultFov; // match viewport fov to default / selected title menu fov if (GetFOV() != fov) { if (m_viewPane) { m_viewPane->OnFOVChanged(fov); SetFOV(fov); } } // Just for editor: Aspect ratio fix when changing the viewport if (!GetIEditor()->IsInGameMode()) { float viewportAspectRatio = float( w ) / h; float targetAspectRatio = GetAspectRatio(); if (targetAspectRatio > viewportAspectRatio) { // Correct for vertical FOV change. float maxTargetHeight = float( w ) / targetAspectRatio; fov = 2 * atanf((h * tan(fov / 2)) / maxTargetHeight); } } m_Camera.SetFrustum(w, h, fov, fNearZ); } GetIEditor()->GetSystem()->SetViewCamera(m_Camera); CGameEngine* ge = GetIEditor()->GetGameEngine(); bool levelIsDisplayable = (ge && ge->IsLevelLoaded() && GetIEditor()->GetDocument() && GetIEditor()->GetDocument()->IsDocumentReady()); //Handle scene render tasks such as gizmos and handles but only when not in VR if (!m_renderer->IsStereoEnabled()) { DisplayContext& displayContext = m_displayContext; PreWidgetRendering(); RenderAll(); // Draw 2D helpers. TransformationMatrices backupSceneMatrices; m_renderer->Set2DMode(m_rcClient.right(), m_rcClient.bottom(), backupSceneMatrices); displayContext.SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); // Display cursor string. RenderCursorString(); if (gSettings.viewports.bShowSafeFrame) { UpdateSafeFrame(); RenderSafeFrame(); } const AzFramework::DisplayContextRequestGuard displayContextGuard(displayContext); AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; AzFramework::DebugDisplayRequestBus::Bind( debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus."); AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); AzFramework::ViewportDebugDisplayEventBus::Event( AzToolsFramework::GetEntityContextId(), &AzFramework::ViewportDebugDisplayEvents::DisplayViewport2d, AzFramework::ViewportInfo{ GetViewportId() }, *debugDisplay); m_renderer->Unset2DMode(backupSceneMatrices); PostWidgetRendering(); } if (levelIsDisplayable) { m_renderer->SetViewport(0, 0, m_renderer->GetWidth(), m_renderer->GetHeight(), m_nCurViewportID); } else { ColorF viewportBackgroundColor(pow(71.0f / 255.0f, 2.2f), pow(71.0f / 255.0f, 2.2f), pow(71.0f / 255.0f, 2.2f)); m_renderer->ClearTargetsLater(FRT_CLEAR_COLOR, viewportBackgroundColor); DrawBackground(); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSelectionRectangle() { if (m_selectedRect.isEmpty()) { return; } Vec3 topLeft(m_selectedRect.left(), m_selectedRect.top(), 1); Vec3 bottomRight(m_selectedRect.right() +1, m_selectedRect.bottom() + 1, 1); m_displayContext.DepthTestOff(); m_displayContext.SetColor(1, 1, 1, 0.4f); m_displayContext.DrawWireBox(topLeft, bottomRight); m_displayContext.DepthTestOn(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::InitDisplayContext() { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); // Draw all objects. DisplayContext& displayContext = m_displayContext; displayContext.settings = GetIEditor()->GetDisplaySettings(); displayContext.view = this; displayContext.renderer = m_renderer; displayContext.box.min = Vec3(-100000.0f, -100000.0f, -100000.0f); displayContext.box.max = Vec3(100000.0f, 100000.0f, 100000.0f); displayContext.camera = &m_Camera; displayContext.flags = 0; if (!displayContext.settings->IsDisplayLabels() || !displayContext.settings->IsDisplayHelpers()) { displayContext.flags |= DISPLAY_HIDENAMES; } if (displayContext.settings->IsDisplayLinks() && displayContext.settings->IsDisplayHelpers()) { displayContext.flags |= DISPLAY_LINKS; } if (m_bDegradateQuality) { displayContext.flags |= DISPLAY_DEGRADATED; } if (displayContext.settings->GetRenderFlags() & RENDER_FLAG_BBOX) { displayContext.flags |= DISPLAY_BBOX; } if (displayContext.settings->IsDisplayTracks() && displayContext.settings->IsDisplayHelpers()) { displayContext.flags |= DISPLAY_TRACKS; displayContext.flags |= DISPLAY_TRACKTICKS; } if (GetIEditor()->GetReferenceCoordSys() == COORDS_WORLD) { displayContext.flags |= DISPLAY_WORLDSPACEAXIS; } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::PopulateEditorGlobalContextMenu(QMenu* /*menu*/, const AZ::Vector2& /*point*/, int /*flags*/) { m_bInMoveMode = false; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderAll() { // Draw all objects. DisplayContext& displayContext = m_displayContext; m_renderer->ResetToDefault(); displayContext.SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); GetIEditor()->GetObjectManager()->Display(displayContext); RenderSelectedRegion(); RenderSnapMarker(); if (gSettings.viewports.bShowGridGuide && GetIEditor()->GetDisplaySettings()->IsDisplayHelpers()) { RenderSnappingGrid(); } if (displayContext.settings->GetDebugFlags() & DBG_MEMINFO) { ProcessMemInfo mi; CProcessInfo::QueryMemInfo(mi); int MB = 1024 * 1024; QString str = QStringLiteral("WorkingSet=%1Mb, PageFile=%2Mb, PageFaults=%3").arg(mi.WorkingSet / MB).arg(mi.PagefileUsage / MB).arg(mi.PageFaultCount); m_renderer->TextToScreenColor(1, 1, 1, 0, 0, 1, str.toUtf8().data()); } { const AzFramework::DisplayContextRequestGuard displayContextGuard(displayContext); AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; AzFramework::DebugDisplayRequestBus::Bind( debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus."); AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); // allow the override of in-editor visualization AzFramework::ViewportDebugDisplayEventBus::Event( AzToolsFramework::GetEntityContextId(), &AzFramework::ViewportDebugDisplayEvents::DisplayViewport, AzFramework::ViewportInfo{ GetViewportId() }, *debugDisplay); m_entityVisibilityQuery.DisplayVisibility(*debugDisplay); if (m_manipulatorManager != nullptr) { using namespace AzToolsFramework::ViewportInteraction; debugDisplay->DepthTestOff(); m_manipulatorManager->DrawManipulators( *debugDisplay, GetCameraState(), BuildMouseInteractionInternal( MouseButtons(TranslateMouseButtons(QGuiApplication::mouseButtons())), BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()), BuildMousePickInternal(WidgetToViewport(mapFromGlobal(QCursor::pos()))))); debugDisplay->DepthTestOn(); } } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::DrawAxis() { AZ_Assert(m_cameraSetForWidgetRenderingCount > 0, "DrawAxis was called but viewport widget rendering was not set. PreWidgetRendering must be called before."); DisplayContext& dc = m_displayContext; // show axis only if draw helpers is activated if (!dc.settings->IsDisplayHelpers()) { return; } Vec3 colX(1, 0, 0), colY(0, 1, 0), colZ(0, 0, 1), colW(1, 1, 1); Vec3 pos(50, 50, 0.1f); // Bottom-left corner float wx, wy, wz; m_renderer->UnProjectFromScreen(pos.x, pos.y, pos.z, &wx, &wy, &wz); Vec3 posInWorld(wx, wy, wz); float screenScale = GetScreenScaleFactor(posInWorld); float length = 0.03f * screenScale; float arrowSize = 0.02f * screenScale; float textSize = 1.1f; Vec3 x(length, 0, 0); Vec3 y(0, length, 0); Vec3 z(0, 0, length); int prevRState = dc.GetState(); dc.DepthWriteOff(); dc.DepthTestOff(); dc.CullOff(); dc.SetLineWidth(1); dc.SetColor(colX); dc.DrawLine(posInWorld, posInWorld + x); dc.DrawArrow(posInWorld + x * 0.9f, posInWorld + x, arrowSize); dc.SetColor(colY); dc.DrawLine(posInWorld, posInWorld + y); dc.DrawArrow(posInWorld + y * 0.9f, posInWorld + y, arrowSize); dc.SetColor(colZ); dc.DrawLine(posInWorld, posInWorld + z); dc.DrawArrow(posInWorld + z * 0.9f, posInWorld + z, arrowSize); dc.SetColor(colW); dc.DrawTextLabel(posInWorld + x, textSize, "x"); dc.DrawTextLabel(posInWorld + y, textSize, "y"); dc.DrawTextLabel(posInWorld + z, textSize, "z"); dc.DepthWriteOn(); dc.DepthTestOn(); dc.CullOn(); dc.SetState(prevRState); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::DrawBackground() { DisplayContext& dc = m_displayContext; if (!dc.settings->IsDisplayHelpers()) // show gradient bg only if draw helpers are activated { return; } int heightVP = m_renderer->GetHeight() - 1; int widthVP = m_renderer->GetWidth() - 1; Vec3 pos(0, 0, 0); Vec3 x(widthVP, 0, 0); Vec3 y(0, heightVP, 0); float height = m_rcClient.height(); Vec3 src = NegY(pos, height); Vec3 trgx = NegY(pos + x, height); Vec3 trgy = NegY(pos + y, height); QColor topColor = palette().color(QPalette::Window); QColor bottomColor = palette().color(QPalette::Disabled, QPalette::WindowText); ColorB firstC(topColor.red(), topColor.green(), topColor.blue(), 255.0f); ColorB secondC(bottomColor.red(), bottomColor.green(), bottomColor.blue(), 255.0f); TransformationMatrices backupSceneMatrices; m_renderer->Set2DMode(m_rcClient.right(), m_rcClient.bottom(), backupSceneMatrices); m_displayContext.SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn); dc.DrawQuadGradient(src, trgx, pos + x, pos, secondC, firstC); m_renderer->Unset2DMode(backupSceneMatrices); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderCursorString() { if (m_cursorStr.isEmpty()) { return; } const auto point = WidgetToViewport(mapFromGlobal(QCursor::pos())); // Display hit object name. float col[4] = { 1, 1, 1, 1 }; m_renderer->Draw2dLabel(point.x() + 12, point.y() + 4, 1.2f, col, false, "%s", m_cursorStr.toUtf8().data()); if (!m_cursorSupplementaryStr.isEmpty()) { float col2[4] = { 1, 1, 0, 1 }; m_renderer->Draw2dLabel(point.x() + 12, point.y() + 4 + CURSOR_FONT_HEIGHT * 1.2f, 1.2f, col2, false, "%s", m_cursorSupplementaryStr.toUtf8().data()); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::UpdateSafeFrame() { m_safeFrame = m_rcClient; if (m_safeFrame.height() == 0) { return; } const bool allowSafeFrameBiggerThanViewport = false; float safeFrameAspectRatio = float( m_safeFrame.width()) / m_safeFrame.height(); float targetAspectRatio = GetAspectRatio(); bool viewportIsWiderThanSafeFrame = (targetAspectRatio <= safeFrameAspectRatio); if (viewportIsWiderThanSafeFrame || allowSafeFrameBiggerThanViewport) { float maxSafeFrameWidth = m_safeFrame.height() * targetAspectRatio; float widthDifference = m_safeFrame.width() - maxSafeFrameWidth; m_safeFrame.setLeft(m_safeFrame.left() + widthDifference * 0.5); m_safeFrame.setRight(m_safeFrame.right() - widthDifference * 0.5); } else { float maxSafeFrameHeight = m_safeFrame.width() / targetAspectRatio; float heightDifference = m_safeFrame.height() - maxSafeFrameHeight; m_safeFrame.setTop(m_safeFrame.top() + heightDifference * 0.5); m_safeFrame.setBottom(m_safeFrame.bottom() - heightDifference * 0.5); } m_safeFrame.adjust(0, 0, -1, -1); // <-- aesthetic improvement. const float SAFE_ACTION_SCALE_FACTOR = 0.05f; m_safeAction = m_safeFrame; m_safeAction.adjust(m_safeFrame.width() * SAFE_ACTION_SCALE_FACTOR, m_safeFrame.height() * SAFE_ACTION_SCALE_FACTOR, -m_safeFrame.width() * SAFE_ACTION_SCALE_FACTOR, -m_safeFrame.height() * SAFE_ACTION_SCALE_FACTOR); const float SAFE_TITLE_SCALE_FACTOR = 0.1f; m_safeTitle = m_safeFrame; m_safeTitle.adjust(m_safeFrame.width() * SAFE_TITLE_SCALE_FACTOR, m_safeFrame.height() * SAFE_TITLE_SCALE_FACTOR, -m_safeFrame.width() * SAFE_TITLE_SCALE_FACTOR, -m_safeFrame.height() * SAFE_TITLE_SCALE_FACTOR); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSafeFrame() { RenderSafeFrame(m_safeFrame, 0.75f, 0.75f, 0, 0.8f); RenderSafeFrame(m_safeAction, 0, 0.85f, 0.80f, 0.8f); RenderSafeFrame(m_safeTitle, 0.80f, 0.60f, 0, 0.8f); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSafeFrame(const QRect& frame, float r, float g, float b, float a) { m_displayContext.SetColor(r, g, b, a); const int LINE_WIDTH = 2; for (int i = 0; i < LINE_WIDTH; i++) { Vec3 topLeft(frame.left() + i, frame.top() + i, 0); Vec3 bottomRight(frame.right() - i, frame.bottom() - i, 0); m_displayContext.DrawWireBox(topLeft, bottomRight); } } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetAspectRatio() const { return gSettings.viewports.fDefaultAspectRatio; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSnapMarker() { if (!gSettings.snap.markerDisplay) { return; } QPoint point = QCursor::pos(); ScreenToClient(point); Vec3 p = MapViewToCP(point); DisplayContext& dc = m_displayContext; float fScreenScaleFactor = GetScreenScaleFactor(p); Vec3 x(1, 0, 0); Vec3 y(0, 1, 0); Vec3 z(0, 0, 1); x = x * gSettings.snap.markerSize * fScreenScaleFactor * 0.1f; y = y * gSettings.snap.markerSize * fScreenScaleFactor * 0.1f; z = z * gSettings.snap.markerSize * fScreenScaleFactor * 0.1f; dc.SetColor(gSettings.snap.markerColor); dc.DrawLine(p - x, p + x); dc.DrawLine(p - y, p + y); dc.DrawLine(p - z, p + z); point = WorldToView(p); int s = 8; dc.DrawLine2d(point + QPoint(-s, -s), point + QPoint(s, -s), 0); dc.DrawLine2d(point + QPoint(-s, s), point + QPoint(s, s), 0); dc.DrawLine2d(point + QPoint(-s, -s), point + QPoint(-s, s), 0); dc.DrawLine2d(point + QPoint(s, -s), point + QPoint(s, s), 0); } ////////////////////////////////////////////////////////////////////////// static void OnMenuDisplayWireframe() { ICVar* piVar(gEnv->pConsole->GetCVar("r_wireframe")); int nRenderMode = piVar->GetIVal(); if (nRenderMode != R_WIREFRAME_MODE) { piVar->Set(R_WIREFRAME_MODE); } else { piVar->Set(R_SOLID_MODE); } } ////////////////////////////////////////////////////////////////////////// static void OnMenuTargetAspectRatio(float aspect) { gSettings.viewports.fDefaultAspectRatio = aspect; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnMenuResolutionCustom() { CCustomResolutionDlg resDlg(width(), height(), parentWidget()); if (resDlg.exec() == QDialog::Accepted) { ResizeView(resDlg.GetWidth(), resDlg.GetHeight()); const QString text = QString::fromLatin1("%1 x %2").arg(resDlg.GetWidth()).arg(resDlg.GetHeight()); QStringList customResPresets; CViewportTitleDlg::LoadCustomPresets("ResPresets", "ResPresetFor2ndView", customResPresets); CViewportTitleDlg::UpdateCustomPresets(text, customResPresets); CViewportTitleDlg::SaveCustomPresets("ResPresets", "ResPresetFor2ndView", customResPresets); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnMenuCreateCameraEntityFromCurrentView() { Camera::EditorCameraSystemRequestBus::Broadcast(&Camera::EditorCameraSystemRequests::CreateCameraEntityFromViewport); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnMenuSelectCurrentCamera() { CBaseObject* pCameraObject = GetCameraObject(); if (pCameraObject && !pCameraObject->IsSelected()) { GetIEditor()->BeginUndo(); IObjectManager* pObjectManager = GetIEditor()->GetObjectManager(); pObjectManager->ClearSelection(); pObjectManager->SelectObject(pCameraObject); GetIEditor()->AcceptUndo("Select Current Camera"); } } static AzFramework::CameraState CameraStateFromCCamera( const CCamera& camera, const float fov, const float width, const float height) { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); AzFramework::CameraState state; state.m_forward = LYVec3ToAZVec3(camera.GetViewdir()); state.m_up = LYVec3ToAZVec3(camera.GetUp()); state.m_side = state.m_forward.Cross(state.m_up); state.m_position = LYVec3ToAZVec3(camera.GetPosition()); state.m_fovOrZoom = fov; state.m_nearClip = camera.GetNearPlane(); state.m_farClip = camera.GetFarPlane(); state.m_orthographic = false; state.m_viewportSize = AZ::Vector2(width, height); return state; } AzFramework::CameraState CRenderViewport::GetCameraState() { return CameraStateFromCCamera(GetCamera(), GetFOV(), m_rcClient.width(), m_rcClient.height()); } bool CRenderViewport::GridSnappingEnabled() { return false; } float CRenderViewport::GridSize() { return 0.0f; } bool CRenderViewport::ShowGrid() { return false; } bool CRenderViewport::AngleSnappingEnabled() { return false; } float CRenderViewport::AngleStep() { return 0.0f; } AZ::Vector3 CRenderViewport::PickTerrain(const AzFramework::ScreenPoint& point) { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true)); } AZ::EntityId CRenderViewport::PickEntity(const AzFramework::ScreenPoint& point) { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); PreWidgetRendering(); AZ::EntityId entityId; HitContext hitInfo; hitInfo.view = this; if (HitTest(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), hitInfo)) { if (hitInfo.object && (hitInfo.object->GetType() == OBJTYPE_AZENTITY)) { auto entityObject = static_cast(hitInfo.object); entityId = entityObject->GetAssociatedEntityId(); } } PostWidgetRendering(); return entityId; } float CRenderViewport::TerrainHeight(const AZ::Vector2& position) { return GetIEditor()->GetTerrainElevation(position.GetX(), position.GetY()); } void CRenderViewport::FindVisibleEntities(AZStd::vector& visibleEntitiesOut) { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); if (ed_visibility_use) { visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End()); } else { if (m_displayContext.GetView() == nullptr) { return; } const AZStd::vector& entityIdCache = m_displayContext.GetView()->GetVisibleObjectsCache()->GetEntityIdCache(); visibleEntitiesOut.assign(entityIdCache.begin(), entityIdCache.end()); } } AzFramework::ScreenPoint CRenderViewport::ViewportWorldToScreen(const AZ::Vector3& worldPosition) { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); PreWidgetRendering(); const AzFramework::ScreenPoint screenPosition = AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(WorldToView(AZVec3ToLYVec3(worldPosition))); PostWidgetRendering(); return screenPosition; } bool CRenderViewport::IsViewportInputFrozen() { return m_freezeViewportInput; } void CRenderViewport::FreezeViewportInput(bool freeze) { m_freezeViewportInput = freeze; } QWidget* CRenderViewport::GetWidgetForViewportContextMenu() { return this; } void CRenderViewport::BeginWidgetContext() { PreWidgetRendering(); } void CRenderViewport::EndWidgetContext() { PostWidgetRendering(); } bool CRenderViewport::ShowingWorldSpace() { using namespace AzToolsFramework::ViewportInteraction; return BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()).Shift(); } void CRenderViewport::SetWindowTitle(const AZStd::string& title) { // Do not support the WindowRequestBus changing the editor window title AZ_UNUSED(title); } AzFramework::WindowSize CRenderViewport::GetClientAreaSize() const { return AzFramework::WindowSize(m_rcClient.width(), m_rcClient.height()); } void CRenderViewport::ResizeClientArea(AzFramework::WindowSize clientAreaSize) { QWidget* window = this->window(); window->resize(aznumeric_cast(clientAreaSize.m_width), aznumeric_cast(clientAreaSize.m_height)); } bool CRenderViewport::GetFullScreenState() const { // CRenderViewport does not currently support full screen. return false; } void CRenderViewport::SetFullScreenState([[maybe_unused]]bool fullScreenState) { // CRenderViewport does not currently support full screen. } bool CRenderViewport::CanToggleFullScreenState() const { // CRenderViewport does not currently support full screen. return false; } void CRenderViewport::ToggleFullScreenState() { // CRenderViewport does not currently support full screen. } void CRenderViewport::ConnectViewportInteractionRequestBus() { AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); m_viewportUi.ConnectViewportUiBus(GetViewportId()); AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect(); } void CRenderViewport::DisconnectViewportInteractionRequestBus() { AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect(); m_viewportUi.DisconnectViewportUiBus(); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::ViewportFreezeRequestBus::Handler::BusDisconnect(); } ////////////////////////////////////////////////////////////////////////// static void ToggleBool(bool* variable, bool* disableVariableIfOn) { *variable = !*variable; if (*variable && disableVariableIfOn) { *disableVariableIfOn = false; } } ////////////////////////////////////////////////////////////////////////// static void ToggleInt(int* variable) { *variable = !*variable; } ////////////////////////////////////////////////////////////////////////// static void AddCheckbox(QMenu* menu, const QString& text, bool* variable, bool* disableVariableIfOn = nullptr) { QAction* action = menu->addAction(text); QObject::connect(action, &QAction::triggered, action, [variable, disableVariableIfOn] { ToggleBool(variable, disableVariableIfOn); }); action->setCheckable(true); action->setChecked(*variable); } ////////////////////////////////////////////////////////////////////////// static void AddCheckbox(QMenu* menu, const QString& text, int* variable) { QAction* action = menu->addAction(text); QObject::connect(action, &QAction::triggered, action, [variable] { ToggleInt(variable); }); action->setCheckable(true); action->setChecked(*variable); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnTitleMenu(QMenu* menu) { const int nWireframe = gEnv->pConsole->GetCVar("r_wireframe")->GetIVal(); QAction* action = menu->addAction(tr("Wireframe")); connect(action, &QAction::triggered, action, OnMenuDisplayWireframe); action->setCheckable(true); action->setChecked(nWireframe == R_WIREFRAME_MODE); const bool bDisplayLabels = GetIEditor()->GetDisplaySettings()->IsDisplayLabels(); action = menu->addAction(tr("Labels")); connect(action, &QAction::triggered, this, [bDisplayLabels] {GetIEditor()->GetDisplaySettings()->DisplayLabels(!bDisplayLabels); }); action->setCheckable(true); action->setChecked(bDisplayLabels); AddCheckbox(menu, tr("Show Safe Frame"), &gSettings.viewports.bShowSafeFrame); AddCheckbox(menu, tr("Show Construction Plane"), &gSettings.snap.constructPlaneDisplay); AddCheckbox(menu, tr("Show Trigger Bounds"), &gSettings.viewports.bShowTriggerBounds); AddCheckbox(menu, tr("Show Icons"), &gSettings.viewports.bShowIcons, &gSettings.viewports.bShowSizeBasedIcons); AddCheckbox(menu, tr("Show Size-based Icons"), &gSettings.viewports.bShowSizeBasedIcons, &gSettings.viewports.bShowIcons); AddCheckbox(menu, tr("Show Helpers of Frozen Objects"), &gSettings.viewports.nShowFrozenHelpers); if (!m_predefinedAspectRatios.IsEmpty()) { QMenu* aspectRatiosMenu = menu->addMenu(tr("Target Aspect Ratio")); for (size_t i = 0; i < m_predefinedAspectRatios.GetCount(); ++i) { const QString& aspectRatioString = m_predefinedAspectRatios.GetName(i); QAction* aspectRatioAction = aspectRatiosMenu->addAction(aspectRatioString); connect(aspectRatioAction, &QAction::triggered, this, [i, this] { OnMenuTargetAspectRatio(m_predefinedAspectRatios.GetValue(i)); }); aspectRatioAction->setCheckable(true); aspectRatioAction->setChecked(m_predefinedAspectRatios.IsCurrent(i)); } } // Set ourself as the active viewport so the following actions create a camera from this view GetIEditor()->GetViewManager()->SelectViewport(this); CGameEngine* gameEngine = GetIEditor()->GetGameEngine(); if (Camera::EditorCameraSystemRequestBus::HasHandlers()) { action = menu->addAction(tr("Create camera entity from current view")); connect(action, &QAction::triggered, this, &CRenderViewport::OnMenuCreateCameraEntityFromCurrentView); if (!gameEngine || !gameEngine->IsLevelLoaded()) { action->setEnabled(false); action->setToolTip(tr(TextCantCreateCameraNoLevel)); menu->setToolTipsVisible(true); } } if (!gameEngine || !gameEngine->IsLevelLoaded()) { action->setEnabled(false); action->setToolTip(tr(TextCantCreateCameraNoLevel)); menu->setToolTipsVisible(true); } if (GetCameraObject()) { action = menu->addAction(tr("Select Current Camera")); connect(action, &QAction::triggered, this, &CRenderViewport::OnMenuSelectCurrentCamera); } // Add Cameras. bool bHasCameras = AddCameraMenuItems(menu); CRenderViewport* pFloatingViewport = nullptr; if (GetIEditor()->GetViewManager()->GetViewCount() > 1) { for (int i = 0; i < GetIEditor()->GetViewManager()->GetViewCount(); ++i) { CViewport* vp = GetIEditor()->GetViewManager()->GetView(i); if (!vp) { continue; } if (viewport_cast(vp) == nullptr) { continue; } if (vp->GetViewportId() == MAX_NUM_VIEWPORTS - 1) { menu->addSeparator(); QMenu* floatViewMenu = menu->addMenu(tr("Floating View")); pFloatingViewport = (CRenderViewport*)vp; pFloatingViewport->AddCameraMenuItems(floatViewMenu); if (bHasCameras) { floatViewMenu->addSeparator(); } QMenu* resolutionMenu = floatViewMenu->addMenu(tr("Resolution")); QStringList customResPresets; CViewportTitleDlg::LoadCustomPresets("ResPresets", "ResPresetFor2ndView", customResPresets); CViewportTitleDlg::AddResolutionMenus(resolutionMenu, [this](int width, int height) { ResizeView(width, height); }, customResPresets); if (!resolutionMenu->actions().isEmpty()) { resolutionMenu->addSeparator(); } QAction* customResolutionAction = resolutionMenu->addAction(tr("Custom...")); connect(customResolutionAction, &QAction::triggered, this, &CRenderViewport::OnMenuResolutionCustom); break; } } } } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::AddCameraMenuItems(QMenu* menu) { if (!menu->isEmpty()) { menu->addSeparator(); } AddCheckbox(menu, "Lock Camera Movement", &m_bLockCameraMovement); menu->addSeparator(); // Camera Sub menu QMenu* customCameraMenu = menu->addMenu(tr("Camera")); QAction* action = customCameraMenu->addAction("Editor Camera"); action->setCheckable(true); action->setChecked(m_viewSourceType == ViewSourceType::None); connect(action, &QAction::triggered, this, &CRenderViewport::SetDefaultCamera); AZ::EBusAggregateResults getCameraResults; Camera::CameraBus::BroadcastResult(getCameraResults, &Camera::CameraRequests::GetCameras); const int numCameras = getCameraResults.values.size(); // only enable if we're editing a sequence in Track View and have cameras in the level bool enableSequenceCameraMenu = (GetIEditor()->GetAnimation()->GetSequence() && numCameras); action = customCameraMenu->addAction(tr("Sequence Camera")); action->setCheckable(true); action->setChecked(m_viewSourceType == ViewSourceType::SequenceCamera); action->setEnabled(enableSequenceCameraMenu); connect(action, &QAction::triggered, this, &CRenderViewport::SetSequenceCamera); QVector additionalCameras; additionalCameras.reserve(getCameraResults.values.size()); for (const AZ::EntityId& entityId : getCameraResults.values) { AZStd::string entityName; AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, entityId); action = new QAction(QString(entityName.c_str()), nullptr); additionalCameras.append(action); action->setCheckable(true); action->setChecked(m_viewEntityId == entityId && m_viewSourceType == ViewSourceType::CameraComponent); connect(action, &QAction::triggered, this, [this, entityId](bool isChecked) { if (isChecked) { SetComponentCamera(entityId); } else { SetDefaultCamera(); } }); } std::sort(additionalCameras.begin(), additionalCameras.end(), [] (QAction* a1, QAction* a2) { return QString::compare(a1->text(), a2->text(), Qt::CaseInsensitive) < 0; }); for (QAction* cameraAction : additionalCameras) { customCameraMenu->addAction(cameraAction); } action = customCameraMenu->addAction(tr("Look through entity")); AzToolsFramework::EntityIdList selectedEntityList; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); action->setCheckable(selectedEntityList.size() > 0 || m_viewSourceType == ViewSourceType::AZ_Entity); action->setEnabled(selectedEntityList.size() > 0 || m_viewSourceType == ViewSourceType::AZ_Entity); action->setChecked(m_viewSourceType == ViewSourceType::AZ_Entity); connect(action, &QAction::triggered, this, [this](bool isChecked) { if (isChecked) { AzToolsFramework::EntityIdList selectedEntityList; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); if (selectedEntityList.size()) { SetEntityAsCamera(*selectedEntityList.begin()); } } else { SetDefaultCamera(); } }); return true; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ResizeView(int width, int height) { const QRect rView = rect().translated(mapToGlobal(QPoint())); int deltaWidth = width - rView.width(); int deltaHeight = height - rView.height(); if (window()->isFullScreen()) { setGeometry(rView.left(), rView.top(), rView.width() + deltaWidth, rView.height() + deltaHeight); } else { QWidget* window = this->window(); if (window->isMaximized()) { window->showNormal(); } const QSize deltaSize = QSize(width, height) - size(); window->move(0, 0); window->resize(window->size() + deltaSize); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ToggleCameraObject() { if (m_viewSourceType == ViewSourceType::SequenceCamera) { ResetToViewSourceType(ViewSourceType::LegacyCamera); } else { ResetToViewSourceType(ViewSourceType::SequenceCamera); } PostCameraSet(); GetIEditor()->GetAnimation()->ForceAnimation(); } void CRenderViewport::OnMouseWheel(Qt::KeyboardModifiers modifiers, short zDelta, const QPoint& point) { using namespace AzToolsFramework::ViewportInteraction; using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; if (GetIEditor()->IsInGameMode() || m_freezeViewportInput) { return; } const auto scaledPoint = WidgetToViewport(point); const auto mouseInteraction = BuildMouseInteractionInternal( MouseButtonsFromButton(MouseButton::None), BuildKeyboardModifiers(modifiers), BuildMousePick(scaledPoint)); bool handled = false; MouseInteractionResult result = MouseInteractionResult::None; AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::EventResult( result, AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions, MouseInteractionEvent(mouseInteraction, zDelta)); handled = result != MouseInteractionResult::None; if (!handled) { Matrix34 m = GetViewTM(); const Vec3 ydir = m.GetColumn1().GetNormalized(); Vec3 pos = m.GetTranslation(); const float posDelta = 0.01f * zDelta * gSettings.wheelZoomSpeed; pos += ydir * posDelta; m_orbitDistance = m_orbitDistance - posDelta; m_orbitDistance = fabs(m_orbitDistance); m.SetTranslation(pos); SetViewTM(m, true); QtViewport::OnMouseWheel(modifiers, zDelta, scaledPoint); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetCamera(const CCamera& camera) { m_Camera = camera; SetViewTM(m_Camera.GetMatrix()); } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetCameraMoveSpeed() const { return gSettings.cameraMoveSpeed; } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetCameraRotateSpeed() const { return gSettings.cameraRotateSpeed; } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::GetCameraInvertYRotation() const { return gSettings.invertYRotation; } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetCameraInvertPan() const { return gSettings.invertPan; } ////////////////////////////////////////////////////////////////////////// CRenderViewport* CRenderViewport::GetPrimaryViewport() { return m_pPrimaryViewport; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::focusOutEvent([[maybe_unused]] QFocusEvent* event) { // if we lose focus, the keyboard map needs to be cleared immediately if (!m_keyDown.isEmpty()) { m_keyDown.clear(); releaseKeyboard(); } } void CRenderViewport::keyPressEvent(QKeyEvent* event) { // Special case Escape key and bubble way up to the top level parent so that it can cancel us out of any active tool // or clear the current selection if (event->key() == Qt::Key_Escape) { QCoreApplication::sendEvent(GetIEditor()->GetEditorMainWindow(), event); } // NOTE: we keep track of keypresses and releases explicitly because the OS/Qt will insert a slight delay between sending // keyevents when the key is held down. This is standard, but makes responding to key events for game style input silly // because we want the movement to be butter smooth. if (!event->isAutoRepeat()) { if (m_keyDown.isEmpty()) { grabKeyboard(); } m_keyDown.insert(event->key()); } QtViewport::keyPressEvent(event); #if defined(AZ_PLATFORM_WINDOWS) // In game mode on windows we need to forward raw text events to the input system. if (GetIEditor()->IsInGameMode() && GetType() == ET_ViewportCamera) { // Get the QString as a '\0'-terminated array of unsigned shorts. // The result remains valid until the string is modified. const ushort* codeUnitsUTF16 = event->text().utf16(); while (ushort codeUnitUTF16 = *codeUnitsUTF16) { AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event, codeUnitUTF16); ++codeUnitsUTF16; } } #endif // defined(AZ_PLATFORM_WINDOWS) } void CRenderViewport::ProcessKeyRelease(QKeyEvent* event) { if (!event->isAutoRepeat()) { if (m_keyDown.contains(event->key())) { m_keyDown.remove(event->key()); if (m_keyDown.isEmpty()) { releaseKeyboard(); } } } } void CRenderViewport::keyReleaseEvent(QKeyEvent* event) { ProcessKeyRelease(event); QtViewport::keyReleaseEvent(event); } void CRenderViewport::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) { Matrix34 camMatrix = viewTM; // If no collision flag set do not check for terrain elevation. if (GetType() == ET_ViewportCamera) { if ((GetIEditor()->GetDisplaySettings()->GetSettings() & SETTINGS_NOCOLLISION) == 0) { Vec3 p = camMatrix.GetTranslation(); bool adjustCameraElevation = true; auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler(); if (terrain) { AZ::Aabb terrainAabb(terrain->GetTerrainAabb()); // Adjust the AABB to include all Z values. Since the goal here is to snap the camera to the terrain height if // it's below the terrain, we only want to verify the camera is within the XY bounds of the terrain to adjust the elevation. terrainAabb.SetMin(AZ::Vector3(terrainAabb.GetMin().GetX(), terrainAabb.GetMin().GetY(), -AZ::Constants::FloatMax)); terrainAabb.SetMax(AZ::Vector3(terrainAabb.GetMax().GetX(), terrainAabb.GetMax().GetY(), AZ::Constants::FloatMax)); if (!terrainAabb.Contains(LYVec3ToAZVec3(p))) { adjustCameraElevation = false; } else if (terrain->GetIsHoleFromFloats(p.x, p.y)) { adjustCameraElevation = false; } } if (adjustCameraElevation) { float z = GetIEditor()->GetTerrainElevation(p.x, p.y); if (p.z < z + 0.25) { p.z = z + 0.25; camMatrix.SetTranslation(p); } } } // Also force this position on game. if (GetIEditor()->GetGameEngine()) { GetIEditor()->GetGameEngine()->SetPlayerViewMatrix(viewTM); } } CBaseObject* cameraObject = GetCameraObject(); if (cameraObject) { // Ignore camera movement if locked. if (IsCameraMovementLocked() || (!GetIEditor()->GetAnimation()->IsRecordMode() && !IsCameraObjectMove())) { return; } AZ::Matrix3x3 lookThroughEntityCorrection = AZ::Matrix3x3::CreateIdentity(); if (m_viewEntityId.IsValid()) { LmbrCentral::EditorCameraCorrectionRequestBus::EventResult( lookThroughEntityCorrection, m_viewEntityId, &LmbrCentral::EditorCameraCorrectionRequests::GetInverseTransformCorrection); } if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { CUndo undo("Move Camera"); if (bMoveOnly) { // specify eObjectUpdateFlags_UserInput so that an undo command gets logged cameraObject->SetWorldPos(camMatrix.GetTranslation(), eObjectUpdateFlags_UserInput); } else { // specify eObjectUpdateFlags_UserInput so that an undo command gets logged cameraObject->SetWorldTM(camMatrix * AZMatrix3x3ToLYMatrix3x3(lookThroughEntityCorrection), eObjectUpdateFlags_UserInput); } } else { if (bMoveOnly) { // Do not specify eObjectUpdateFlags_UserInput, so that an undo command does not get logged; we covered it already when m_pressedKeyState was PressedThisFrame cameraObject->SetWorldPos(camMatrix.GetTranslation()); } else { // Do not specify eObjectUpdateFlags_UserInput, so that an undo command does not get logged; we covered it already when m_pressedKeyState was PressedThisFrame cameraObject->SetWorldTM(camMatrix * AZMatrix3x3ToLYMatrix3x3(lookThroughEntityCorrection)); } } using namespace AzToolsFramework; ComponentEntityObjectRequestBus::Event(cameraObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache); } else if (m_viewEntityId.IsValid()) { // Ignore camera movement if locked. if (IsCameraMovementLocked() || (!GetIEditor()->GetAnimation()->IsRecordMode() && !IsCameraObjectMove())) { return; } if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { CUndo undo("Move Camera"); if (bMoveOnly) { AZ::TransformBus::Event( m_viewEntityId, &AZ::TransformInterface::SetWorldTranslation, LYVec3ToAZVec3(camMatrix.GetTranslation())); } else { AZ::TransformBus::Event( m_viewEntityId, &AZ::TransformInterface::SetWorldTM, LYTransformToAZTransform(camMatrix)); } } else { if (bMoveOnly) { AZ::TransformBus::Event( m_viewEntityId, &AZ::TransformInterface::SetWorldTranslation, LYVec3ToAZVec3(camMatrix.GetTranslation())); } else { AZ::TransformBus::Event( m_viewEntityId, &AZ::TransformInterface::SetWorldTM, LYTransformToAZTransform(camMatrix)); } } AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast( &AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); } if (m_pressedKeyState == KeyPressedState::PressedThisFrame) { m_pressedKeyState = KeyPressedState::PressedInPreviousFrame; } QtViewport::SetViewTM(camMatrix); m_Camera.SetMatrix(camMatrix); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSelectedRegion() { AABB box; GetIEditor()->GetSelectedRegion(box); if (box.IsEmpty()) { return; } float x1 = box.min.x; float y1 = box.min.y; float x2 = box.max.x; float y2 = box.max.y; DisplayContext& dc = m_displayContext; float fMaxSide = MAX(y2 - y1, x2 - x1); if (fMaxSide < 0.1f) { return; } float fStep = fMaxSide / 100.0f; float fMinZ = 0; float fMaxZ = 0; // Draw yellow border lines. dc.SetColor(1, 1, 0, 1); float offset = 0.01f; Vec3 p1, p2; const float defaultTerrainHeight = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler(); for (float y = y1; y < y2; y += fStep) { p1.x = x1; p1.y = y; p1.z = terrain ? terrain->GetHeightFromFloats(p1.x, p1.y) + offset : defaultTerrainHeight + offset; p2.x = x1; p2.y = y + fStep; p2.z = terrain ? terrain->GetHeightFromFloats(p2.x, p2.y) + offset : defaultTerrainHeight + offset; dc.DrawLine(p1, p2); p1.x = x2; p1.y = y; p1.z = terrain ? terrain->GetHeightFromFloats(p1.x, p1.y) + offset : defaultTerrainHeight + offset; p2.x = x2; p2.y = y + fStep; p2.z = terrain ? terrain->GetHeightFromFloats(p2.x, p2.y) + offset : defaultTerrainHeight + offset; dc.DrawLine(p1, p2); fMinZ = min(fMinZ, min(p1.z, p2.z)); fMaxZ = max(fMaxZ, max(p1.z, p2.z)); } for (float x = x1; x < x2; x += fStep) { p1.x = x; p1.y = y1; p1.z = terrain ? terrain->GetHeightFromFloats(p1.x, p1.y) + offset : defaultTerrainHeight + offset; p2.x = x + fStep; p2.y = y1; p2.z = terrain ? terrain->GetHeightFromFloats(p2.x, p2.y) + offset : defaultTerrainHeight + offset; dc.DrawLine(p1, p2); p1.x = x; p1.y = y2; p1.z = terrain ? terrain->GetHeightFromFloats(p1.x, p1.y) + offset : defaultTerrainHeight + offset; p2.x = x + fStep; p2.y = y2; p2.z = terrain ? terrain->GetHeightFromFloats(p2.x, p2.y) + offset : defaultTerrainHeight + offset; dc.DrawLine(p1, p2); fMinZ = min(fMinZ, min(p1.z, p2.z)); fMaxZ = max(fMaxZ, max(p1.z, p2.z)); } { // Draw a box area float fBoxOver = fMaxSide / 5.0f; float fBoxHeight = fBoxOver + fMaxZ - fMinZ; ColorB boxColor(64, 64, 255, 128); // light blue ColorB transparent(boxColor.r, boxColor.g, boxColor.b, 0); Vec3 base[] = { Vec3(x1, y1, fMinZ), Vec3(x2, y1, fMinZ), Vec3(x2, y2, fMinZ), Vec3(x1, y2, fMinZ) }; // Generate vertices static AABB boxPrev(AABB::RESET); static std::vector verts; static std::vector colors; if (!IsEquivalent(boxPrev, box)) { verts.resize(0); colors.resize(0); for (int i = 0; i < 4; ++i) { Vec3& p = base[i]; verts.push_back(p); verts.push_back(Vec3(p.x, p.y, p.z + fBoxHeight)); verts.push_back(Vec3(p.x, p.y, p.z + fBoxHeight + fBoxOver)); colors.push_back(boxColor); colors.push_back(boxColor); colors.push_back(transparent); } boxPrev = box; } // Generate indices const int numInds = 4 * 12; static vtx_idx inds[numInds]; static bool bNeedIndsInit = true; if (bNeedIndsInit) { vtx_idx* pInds = &inds[0]; for (int i = 0; i < 4; ++i) { int over = 0; if (i == 3) { over = -12; } int ind = i * 3; *pInds++ = ind; *pInds++ = ind + 3 + over; *pInds++ = ind + 1; *pInds++ = ind + 1; *pInds++ = ind + 3 + over; *pInds++ = ind + 4 + over; ind = i * 3 + 1; *pInds++ = ind; *pInds++ = ind + 3 + over; *pInds++ = ind + 1; *pInds++ = ind + 1; *pInds++ = ind + 3 + over; *pInds++ = ind + 4 + over; } bNeedIndsInit = false; } // Draw lines for (int i = 0; i < 4; ++i) { Vec3& p = base[i]; dc.DrawLine(p, Vec3(p.x, p.y, p.z + fBoxHeight), ColorF(1, 1, 0, 1), ColorF(1, 1, 0, 1)); dc.DrawLine(Vec3(p.x, p.y, p.z + fBoxHeight), Vec3(p.x, p.y, p.z + fBoxHeight + fBoxOver), ColorF(1, 1, 0, 1), ColorF(1, 1, 0, 0)); } // Draw volume dc.DepthWriteOff(); dc.CullOff(); dc.pRenderAuxGeom->DrawTriangles(&verts[0], verts.size(), &inds[0], numInds, &colors[0]); dc.CullOn(); dc.DepthWriteOn(); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ProcessKeys() { FUNCTION_PROFILER(GetIEditor()->GetSystem(), PROFILE_EDITOR); if (m_PlayerControl || GetIEditor()->IsInGameMode() || !CheckRespondToInput() || m_freezeViewportInput) { return; } //m_Camera.UpdateFrustum(); Matrix34 m = GetViewTM(); Vec3 ydir = m.GetColumn1().GetNormalized(); Vec3 xdir = m.GetColumn0().GetNormalized(); Vec3 zdir = m.GetColumn2().GetNormalized(); Vec3 pos = GetViewTM().GetTranslation(); float speedScale = AZStd::GetMin( 60.0f * GetIEditor()->GetSystem()->GetITimer()->GetFrameTime(), 20.0f); speedScale *= GetCameraMoveSpeed(); // Use the global modifier keys instead of our keymap. It's more reliable. const bool shiftPressed = QGuiApplication::queryKeyboardModifiers() & Qt::ShiftModifier; const bool controlPressed = QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier; if (shiftPressed) { speedScale *= gSettings.cameraFastMoveSpeed; } if (controlPressed) { return; } bool bIsPressedSome = false; if (IsKeyDown(Qt::Key_Up) || IsKeyDown(Qt::Key_W)) { // move forward bIsPressedSome = true; pos = pos + (speedScale * m_moveSpeed * ydir); } if (IsKeyDown(Qt::Key_Down) || IsKeyDown(Qt::Key_S)) { // move backward bIsPressedSome = true; pos = pos - (speedScale * m_moveSpeed * ydir); } if (IsKeyDown(Qt::Key_Left) || IsKeyDown(Qt::Key_A)) { // move left bIsPressedSome = true; pos = pos - (speedScale * m_moveSpeed * xdir); } if (IsKeyDown(Qt::Key_Right) || IsKeyDown(Qt::Key_D)) { // move right bIsPressedSome = true; pos = pos + (speedScale * m_moveSpeed * xdir); } if (IsKeyDown(Qt::Key_E)) { // move Up bIsPressedSome = true; pos = pos + (speedScale * m_moveSpeed * zdir); } if (IsKeyDown(Qt::Key_Q)) { // move down bIsPressedSome = true; pos = pos - (speedScale * m_moveSpeed * zdir); } if (bIsPressedSome) { // Only change the keystate to pressed if it wasn't already marked in // a previous frame. Otherwise, the undo/redo stack will be all off // from what SetViewTM() does. if (m_pressedKeyState == KeyPressedState::AllUp) { m_pressedKeyState = KeyPressedState::PressedThisFrame; } m.SetTranslation(pos); SetViewTM(m, true); } bool mouseModifierKeysDown = ((QGuiApplication::mouseButtons() & (Qt::RightButton | Qt::MiddleButton)) != 0); if (!bIsPressedSome && !mouseModifierKeysDown) { m_pressedKeyState = KeyPressedState::AllUp; } } Vec3 CRenderViewport::WorldToView3D(const Vec3& wp, [[maybe_unused]] int nFlags) const { AZ_Assert(m_cameraSetForWidgetRenderingCount > 0, "WorldToView3D was called but viewport widget rendering was not set. PreWidgetRendering must be called before."); Vec3 out(0, 0, 0); float x, y, z; m_renderer->ProjectToScreen(wp.x, wp.y, wp.z, &x, &y, &z); if (_finite(x) && _finite(y) && _finite(z)) { out.x = (x / 100) * m_rcClient.width(); out.y = (y / 100) * m_rcClient.height(); out.x /= QHighDpiScaling::factor(windowHandle()->screen()); out.y /= QHighDpiScaling::factor(windowHandle()->screen()); out.z = z; } return out; } ////////////////////////////////////////////////////////////////////////// QPoint CRenderViewport::WorldToView(const Vec3& wp) const { AZ_Assert(m_cameraSetForWidgetRenderingCount > 0, "WorldToView was called but viewport widget rendering was not set. PreWidgetRendering must be called before."); QPoint p; float x, y, z; m_renderer->ProjectToScreen(wp.x, wp.y, wp.z, &x, &y, &z); if (_finite(x) || _finite(y)) { p.rx() = (x / 100) * m_rcClient.width(); p.ry() = (y / 100) * m_rcClient.height(); } else { QPoint(0, 0); } return p; } ////////////////////////////////////////////////////////////////////////// QPoint CRenderViewport::WorldToViewParticleEditor(const Vec3& wp, int width, int height) const { QPoint p; float x, y, z; m_renderer->ProjectToScreen(wp.x, wp.y, wp.z, &x, &y, &z); if (_finite(x) || _finite(y)) { p.rx() = (x / 100) * width; p.ry() = (y / 100) * height; } else { QPoint(0, 0); } return p; } ////////////////////////////////////////////////////////////////////////// Vec3 CRenderViewport::ViewToWorld(const QPoint& vp, bool* collideWithTerrain, bool onlyTerrain, bool bSkipVegetation, bool bTestRenderMesh, bool* collideWithObject) const { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor); // Make sure we initialize the value if a pointer has been passed in if (collideWithTerrain != nullptr) { *collideWithTerrain = false; } // Make sure we initialize the value if a pointer has been passed in if (collideWithObject != nullptr) { *collideWithObject = false; } if (!m_renderer) { return Vec3(0, 0, 0); } QRect rc = m_rcClient; Vec3 pos0; if (!m_Camera.Unproject(Vec3(vp.x(), rc.bottom() - vp.y(), 0), pos0)) { return Vec3(0, 0, 0); } if (!IsVectorInValidRange(pos0)) { pos0.Set(0, 0, 0); } Vec3 pos1; if (!m_Camera.Unproject(Vec3(vp.x(), rc.bottom() - vp.y(), 1), pos1)) { return Vec3(0, 0, 0); } if (!IsVectorInValidRange(pos1)) { pos1.Set(1, 0, 0); } const float maxDistance = 10000.f; Vec3 v = (pos1 - pos0); v = v.GetNormalized(); v = v * maxDistance; if (!_finite(v.x) || !_finite(v.y) || !_finite(v.z)) { return Vec3(0, 0, 0); } Vec3 colp = pos0 + 0.002f * v; AZ_UNUSED(vp) AZ_UNUSED(bTestRenderMesh) AZ_UNUSED(bSkipVegetation) AZ_UNUSED(bSkipVegetation) AZStd::optional> hitDistancePosition; if (!onlyTerrain && !GetIEditor()->IsTerrainAxisIgnoreObjects()) { AzFramework::EntityContextId editorContextId; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( editorContextId, &AzToolsFramework::EditorEntityContextRequests::GetEditorEntityContextId); AzFramework::RenderGeometry::RayRequest ray; ray.m_startWorldPosition = LYVec3ToAZVec3(pos0); ray.m_endWorldPosition = LYVec3ToAZVec3(pos0 + v); ray.m_onlyVisible = true; AzFramework::RenderGeometry::RayResult result; AzFramework::RenderGeometry::IntersectorBus::EventResult(result, editorContextId, &AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, ray); if (result) { if (!hitDistancePosition || result.m_distance < hitDistancePosition->first) { hitDistancePosition = {result.m_distance, result.m_worldPosition}; if (collideWithObject) { *collideWithObject = true; } } } } if (hitDistancePosition) { colp = AZVec3ToLYVec3(hitDistancePosition->second); } return colp; } ////////////////////////////////////////////////////////////////////////// Vec3 CRenderViewport::ViewToWorldNormal(const QPoint& vp, bool onlyTerrain, bool bTestRenderMesh) { AZ_UNUSED(vp) AZ_UNUSED(bTestRenderMesh) AZ_Assert(m_cameraSetForWidgetRenderingCount > 0, "ViewToWorldNormal was called but viewport widget rendering was not set. PreWidgetRendering must be called before."); AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor); if (!m_renderer) { return Vec3(0, 0, 1); } QRect rc = m_rcClient; Vec3 pos0, pos1; float wx, wy, wz; m_renderer->UnProjectFromScreen(vp.x(), rc.bottom() - vp.y(), 0, &wx, &wy, &wz); if (!_finite(wx) || !_finite(wy) || !_finite(wz)) { return Vec3(0, 0, 1); } pos0(wx, wy, wz); if (!IsVectorInValidRange(pos0)) { pos0.Set(0, 0, 0); } m_renderer->UnProjectFromScreen(vp.x(), rc.bottom() - vp.y(), 1, &wx, &wy, &wz); if (!_finite(wx) || !_finite(wy) || !_finite(wz)) { return Vec3(0, 0, 1); } pos1(wx, wy, wz); Vec3 v = (pos1 - pos0); if (!IsVectorInValidRange(pos1)) { pos1.Set(1, 0, 0); } const float maxDistance = 2000.f; v = v.GetNormalized(); v = v * maxDistance; if (!_finite(v.x) || !_finite(v.y) || !_finite(v.z)) { return Vec3(0, 0, 1); } Vec3 colp(0, 0, 0); AZStd::optional> hitDistanceNormal; if (!onlyTerrain && !GetIEditor()->IsTerrainAxisIgnoreObjects()) { AzFramework::EntityContextId editorContextId; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( editorContextId, &AzToolsFramework::EditorEntityContextRequests::GetEditorEntityContextId); AzFramework::RenderGeometry::RayRequest ray; ray.m_startWorldPosition = LYVec3ToAZVec3(pos0); ray.m_endWorldPosition = LYVec3ToAZVec3(pos0 + v); ray.m_onlyVisible = true; AzFramework::RenderGeometry::RayResult result; AzFramework::RenderGeometry::IntersectorBus::EventResult(result, editorContextId, &AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, ray); if (result) { if (!hitDistanceNormal || result.m_distance < hitDistanceNormal->first) { hitDistanceNormal = { result.m_distance, result.m_worldNormal }; } } } return hitDistanceNormal ? AZVec3ToLYVec3(hitDistanceNormal->second) : Vec3(0, 0, 1); } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::AdjustObjectPosition(const ray_hit& hit, Vec3& outNormal, Vec3& outPos) const { Matrix34A objMat, objMatInv; Matrix33 objRot, objRotInv; if (hit.pCollider->GetiForeignData() != PHYS_FOREIGN_ID_STATIC) { return false; } IRenderNode* pNode = (IRenderNode*) hit.pCollider->GetForeignData(PHYS_FOREIGN_ID_STATIC); if (!pNode || !pNode->GetEntityStatObj()) { return false; } IStatObj* pEntObject = pNode->GetEntityStatObj(hit.partid, 0, &objMat, false); if (!pEntObject || !pEntObject->GetRenderMesh()) { return false; } objRot = Matrix33(objMat); objRot.NoScale(); // No scale. objRotInv = objRot; objRotInv.Invert(); float fWorldScale = objMat.GetColumn(0).GetLength(); // GetScale float fWorldScaleInv = 1.0f / fWorldScale; // transform decal into object space objMatInv = objMat; objMatInv.Invert(); // put into normal object space hit direction of projection Vec3 invhitn = -(hit.n); Vec3 vOS_HitDir = objRotInv.TransformVector(invhitn).GetNormalized(); // put into position object space hit position Vec3 vOS_HitPos = objMatInv.TransformPoint(hit.pt); vOS_HitPos -= vOS_HitDir * RENDER_MESH_TEST_DISTANCE * fWorldScaleInv; IRenderMesh* pRM = pEntObject->GetRenderMesh(); AABB aabbRNode; pRM->GetBBox(aabbRNode.min, aabbRNode.max); Vec3 vOut(0, 0, 0); if (!Intersect::Ray_AABB(Ray(vOS_HitPos, vOS_HitDir), aabbRNode, vOut)) { return false; } if (!pRM || !pRM->GetVerticesCount()) { return false; } if (RayRenderMeshIntersection(pRM, vOS_HitPos, vOS_HitDir, outPos, outNormal)) { outNormal = objRot.TransformVector(outNormal).GetNormalized(); outPos = objMat.TransformPoint(outPos); return true; } return false; } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::RayRenderMeshIntersection(IRenderMesh*, const Vec3&, const Vec3&, Vec3&, Vec3&) const { return false; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const { AZ_Assert(m_cameraSetForWidgetRenderingCount > 0, "ViewToWorldRay was called but SScopedCurrentContext was not set at a higher scope! This means the camera for this call is incorrect."); if (!m_renderer) { return; } QRect rc = m_rcClient; Vec3 pos0, pos1; float wx, wy, wz; m_renderer->UnProjectFromScreen(vp.x(), rc.bottom() - vp.y(), 0, &wx, &wy, &wz); if (!_finite(wx) || !_finite(wy) || !_finite(wz)) { return; } if (fabs(wx) > 1000000 || fabs(wy) > 1000000 || fabs(wz) > 1000000) { return; } pos0(wx, wy, wz); m_renderer->UnProjectFromScreen(vp.x(), rc.bottom() - vp.y(), 1, &wx, &wy, &wz); if (!_finite(wx) || !_finite(wy) || !_finite(wz)) { return; } if (fabs(wx) > 1000000 || fabs(wy) > 1000000 || fabs(wz) > 1000000) { return; } pos1(wx, wy, wz); Vec3 v = (pos1 - pos0); v = v.GetNormalized(); raySrc = pos0; rayDir = v; } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetScreenScaleFactor(const Vec3& worldPoint) const { float dist = m_Camera.GetPosition().GetDistance(worldPoint); if (dist < m_Camera.GetNearPlane()) { dist = m_Camera.GetNearPlane(); } return dist; } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetScreenScaleFactor(const CCamera& camera, const Vec3& object_position) { Vec3 camPos = camera.GetPosition(); float dist = camPos.GetDistance(object_position); return dist; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnDestroy() { DestroyRenderContext(); } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::CheckRespondToInput() const { if (!Editor::EditorQtApplication::IsActive()) { return false; } if (!hasFocus()) { return false; } return true; } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::HitTest(const QPoint& point, HitContext& hitInfo) { hitInfo.camera = &m_Camera; hitInfo.pExcludedObject = GetCameraObject(); return QtViewport::HitTest(point, hitInfo); } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::IsBoundsVisible(const AABB& box) const { // If at least part of bbox is visible then its visible. return m_Camera.IsAABBVisible_F(AABB(box.min, box.max)); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::CenterOnSelection() { if (!GetIEditor()->GetSelection()->IsEmpty()) { // Get selection bounds & center CSelectionGroup* sel = GetIEditor()->GetSelection(); AABB selectionBounds = sel->GetBounds(); CenterOnAABB(selectionBounds); } } void CRenderViewport::CenterOnAABB(const AABB& aabb) { Vec3 selectionCenter = aabb.GetCenter(); // Minimum center size is 40cm const float minSelectionRadius = 0.4f; const float selectionSize = std::max(minSelectionRadius, aabb.GetRadius()); // Move camera 25% further back than required const float centerScale = 1.25f; // Decompose original transform matrix const Matrix34& originalTM = GetViewTM(); AffineParts affineParts; affineParts.SpectralDecompose(originalTM); // Forward vector is y component of rotation matrix Matrix33 rotationMatrix(affineParts.rot); const Vec3 viewDirection = rotationMatrix.GetColumn1().GetNormalized(); // Compute adjustment required by FOV != 90 degrees const float fov = GetFOV(); const float fovScale = (1.0f / tan(fov * 0.5f)); // Compute new transform matrix const float distanceToTarget = selectionSize * fovScale * centerScale; const Vec3 newPosition = selectionCenter - (viewDirection * distanceToTarget); Matrix34 newTM = Matrix34(rotationMatrix, newPosition); // Set new orbit distance m_orbitDistance = distanceToTarget; m_orbitDistance = fabs(m_orbitDistance); SetViewTM(newTM); } void CRenderViewport::CenterOnSliceInstance() { AzToolsFramework::EntityIdList selectedEntityList; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); AZ::SliceComponent::SliceInstanceAddress sliceAddress; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(sliceAddress, &AzToolsFramework::ToolsApplicationRequestBus::Events::FindCommonSliceInstanceAddress, selectedEntityList); if (!sliceAddress.IsValid()) { return; } AZ::EntityId sliceRootEntityId; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(sliceRootEntityId, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetRootEntityIdOfSliceInstance, sliceAddress); if (!sliceRootEntityId.IsValid()) { return; } AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList{sliceRootEntityId}); const AZ::SliceComponent::InstantiatedContainer* instantiatedContainer = sliceAddress.GetInstance()->GetInstantiated(); AABB aabb(Vec3(std::numeric_limits::max()), Vec3(-std::numeric_limits::max())); for (AZ::Entity* entity : instantiatedContainer->m_entities) { CEntityObject* entityObject = nullptr; AzToolsFramework::ComponentEntityEditorRequestBus::EventResult(entityObject, entity->GetId(), &AzToolsFramework::ComponentEntityEditorRequestBus::Events::GetSandboxObject); AABB box; entityObject->GetBoundBox(box); aabb.Add(box.min); aabb.Add(box.max); } CenterOnAABB(aabb); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetFOV(float fov) { if (m_pCameraFOVVariable) { m_pCameraFOVVariable->Set(fov); } else { m_camFOV = fov; } if (m_viewPane) { m_viewPane->OnFOVChanged(fov); } } ////////////////////////////////////////////////////////////////////////// float CRenderViewport::GetFOV() const { if (m_viewSourceType == ViewSourceType::SequenceCamera) { CBaseObject* cameraObject = GetCameraObject(); AZ::EntityId cameraEntityId; AzToolsFramework::ComponentEntityObjectRequestBus::EventResult(cameraEntityId, cameraObject, &AzToolsFramework::ComponentEntityObjectRequestBus::Events::GetAssociatedEntityId); if (cameraEntityId.IsValid()) { // component Camera float fov = DEFAULT_FOV; Camera::CameraRequestBus::EventResult(fov, cameraEntityId, &Camera::CameraComponentRequests::GetFov); return AZ::DegToRad(fov); } } if (m_pCameraFOVVariable) { float fov; m_pCameraFOVVariable->Get(fov); return fov; } else if (m_viewEntityId.IsValid()) { float fov = AZ::RadToDeg(m_camFOV); Camera::CameraRequestBus::EventResult(fov, m_viewEntityId, &Camera::CameraComponentRequests::GetFov); return AZ::DegToRad(fov); } return m_camFOV; } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::CreateRenderContext() { // Create context. if (m_renderer && !m_bRenderContextCreated) { m_bRenderContextCreated = true; AzFramework::WindowRequestBus::Handler::BusConnect(renderOverlayHWND()); AzFramework::WindowSystemNotificationBus::Broadcast(&AzFramework::WindowSystemNotificationBus::Handler::OnWindowCreated, renderOverlayHWND()); WIN_HWND oldContext = m_renderer->GetCurrentContextHWND(); m_renderer->CreateContext(renderOverlayHWND()); m_renderer->SetCurrentContext(oldContext); // restore prior context return true; } return false; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::DestroyRenderContext() { // Destroy render context. if (m_renderer && m_bRenderContextCreated) { // Do not delete primary context. if (m_hwnd != m_renderer->GetHWND()) { m_renderer->DeleteContext(m_hwnd); } m_bRenderContextCreated = false; } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetDefaultCamera() { if (IsDefaultCamera()) { return; } ResetToViewSourceType(ViewSourceType::None); GetViewManager()->SetCameraObjectId(m_cameraObjectId); SetName(m_defaultViewName); SetViewTM(m_defaultViewTM); PostCameraSet(); } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::IsDefaultCamera() const { return m_viewSourceType == ViewSourceType::None; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetSequenceCamera() { if (m_viewSourceType == ViewSourceType::SequenceCamera) { // Reset if we were checked before SetDefaultCamera(); } else { ResetToViewSourceType(ViewSourceType::SequenceCamera); SetName(tr("Sequence Camera")); SetViewTM(GetViewTM()); GetViewManager()->SetCameraObjectId(m_cameraObjectId); PostCameraSet(); // ForceAnimation() so Track View will set the Camera params // if a camera is animated in the sequences. if (GetIEditor() && GetIEditor()->GetAnimation()) { GetIEditor()->GetAnimation()->ForceAnimation(); } } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetComponentCamera(const AZ::EntityId& entityId) { ResetToViewSourceType(ViewSourceType::CameraComponent); SetViewEntity(entityId); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetEntityAsCamera(const AZ::EntityId& entityId, bool lockCameraMovement) { ResetToViewSourceType(ViewSourceType::AZ_Entity); SetViewEntity(entityId, lockCameraMovement); } void CRenderViewport::SetFirstComponentCamera() { AZ::EBusAggregateResults results; Camera::CameraBus::BroadcastResult(results, &Camera::CameraRequests::GetCameras); AZStd::sort_heap(results.values.begin(), results.values.end()); AZ::EntityId entityId; if (results.values.size() > 0) { entityId = results.values[0]; } SetComponentCamera(entityId); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::SetSelectedCamera() { AZ::EBusAggregateResults cameraList; Camera::CameraBus::BroadcastResult(cameraList, &Camera::CameraRequests::GetCameras); if (cameraList.values.size() > 0) { AzToolsFramework::EntityIdList selectedEntityList; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); for (const AZ::EntityId& entityId : selectedEntityList) { if (AZStd::find(cameraList.values.begin(), cameraList.values.end(), entityId) != cameraList.values.end()) { SetComponentCamera(entityId); } } } } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::IsSelectedCamera() const { CBaseObject* pCameraObject = GetCameraObject(); if (pCameraObject && pCameraObject == GetIEditor()->GetSelectedObject()) { return true; } AzToolsFramework::EntityIdList selectedEntityList; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult( selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); if ((m_viewSourceType == ViewSourceType::CameraComponent || m_viewSourceType == ViewSourceType::AZ_Entity) && !selectedEntityList.empty() && AZStd::find(selectedEntityList.begin(), selectedEntityList.end(), m_viewEntityId) != selectedEntityList.end()) { return true; } return false; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::CycleCamera() { // None -> Sequence -> LegacyCamera -> ... LegacyCamera -> CameraComponent -> ... CameraComponent -> None // AZ_Entity has been intentionally left out of the cycle for now. switch (m_viewSourceType) { case CRenderViewport::ViewSourceType::None: { SetFirstComponentCamera(); break; } case CRenderViewport::ViewSourceType::SequenceCamera: { AZ_Error("CRenderViewport", false, "Legacy cameras no longer exist, unable to set sequence camera."); break; } case CRenderViewport::ViewSourceType::LegacyCamera: { AZ_Warning("CRenderViewport", false, "Legacy cameras no longer exist, using first found component camera instead."); SetFirstComponentCamera(); break; } case CRenderViewport::ViewSourceType::CameraComponent: { AZ::EBusAggregateResults results; Camera::CameraBus::BroadcastResult(results, &Camera::CameraRequests::GetCameras); AZStd::sort_heap(results.values.begin(), results.values.end()); auto&& currentCameraIterator = AZStd::find(results.values.begin(), results.values.end(), m_viewEntityId); if (currentCameraIterator != results.values.end()) { ++currentCameraIterator; if (currentCameraIterator != results.values.end()) { SetComponentCamera(*currentCameraIterator); break; } } SetDefaultCamera(); break; } case CRenderViewport::ViewSourceType::AZ_Entity: { // we may decide to have this iterate over just selected entities SetDefaultCamera(); break; } default: { SetDefaultCamera(); break; } } } void CRenderViewport::SetViewFromEntityPerspective(const AZ::EntityId& entityId) { SetViewAndMovementLockFromEntityPerspective(entityId, false); } void CRenderViewport::SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) { if (!m_ignoreSetViewFromEntityPerspective) { SetEntityAsCamera(entityId, lockCameraMovement); } } bool CRenderViewport::GetActiveCameraPosition(AZ::Vector3& cameraPos) { cameraPos = LYVec3ToAZVec3(m_viewTM.GetTranslation()); return true; } bool CRenderViewport::GetActiveCameraState(AzFramework::CameraState& cameraState) { if (m_pPrimaryViewport == this) { if (GetIEditor()->IsInGameMode()) { return false; } else { const auto& camera = GetCamera(); cameraState = CameraStateFromCCamera(camera, GetFOV(), m_rcClient.width(), m_rcClient.height()); } return true; } return false; } void CRenderViewport::OnStartPlayInEditor() { if (m_viewEntityId.IsValid()) { m_viewEntityIdCachedForEditMode = m_viewEntityId; AZ::EntityId runtimeEntityId; AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequestBus::Events::MapEditorIdToRuntimeId, m_viewEntityId, runtimeEntityId); m_viewEntityId = runtimeEntityId; } // Force focus the render viewport, otherwise we don't receive keyPressEvents until the user first clicks a // mouse button. See also CRenderViewport::mousePressEvent for a deatiled description of the underlying bug. // We need to queue this up because we don't actually lose focus until sometime after this function returns. QTimer::singleShot(0, this, &CRenderViewport::ActivateWindowAndSetFocus); } void CRenderViewport::OnStopPlayInEditor() { if (m_viewEntityIdCachedForEditMode.IsValid()) { m_viewEntityId = m_viewEntityIdCachedForEditMode; m_viewEntityIdCachedForEditMode.SetInvalid(); } } void CRenderViewport::ActivateWindowAndSetFocus() { window()->activateWindow(); setFocus(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderConstructionPlane() { // noop } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RenderSnappingGrid() { // noop } ////////////////////////////////////////////////////////////////////////// CRenderViewport::SPreviousContext CRenderViewport::SetCurrentContext(int newWidth, int newHeight) const { SPreviousContext x; x.window = reinterpret_cast(m_renderer->GetCurrentContextHWND()); x.mainViewport = m_renderer->IsCurrentContextMainVP(); x.width = m_renderer->GetCurrentContextViewportWidth(); x.height = m_renderer->GetCurrentContextViewportHeight(); x.rendererCamera = m_renderer->GetCamera(); const float scale = CLAMP(gEnv->pConsole->GetCVar("r_ResolutionScale")->GetFVal(), MIN_RESOLUTION_SCALE, MAX_RESOLUTION_SCALE); const QSize newSize = WidgetToViewport(QSize(newWidth, newHeight)) * scale; // No way to query the requested Qt scale here, so do it this way for now float widthScale = aznumeric_cast(newSize.width()) / aznumeric_cast(newWidth); float heightScale = aznumeric_cast(newSize.height()) / aznumeric_cast(newHeight); m_renderer->SetCurrentContext(renderOverlayHWND()); m_renderer->ChangeViewport(0, 0, newWidth, newHeight, true, widthScale, heightScale); m_renderer->SetCamera(m_Camera); return x; } ////////////////////////////////////////////////////////////////////////// CRenderViewport::SPreviousContext CRenderViewport::SetCurrentContext() const { const auto r = rect(); return SetCurrentContext(r.width(), r.height()); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::RestorePreviousContext(const SPreviousContext& x) const { if (x.window && x.window != m_renderer->GetCurrentContextHWND()) { m_renderer->SetCurrentContext(x.window); m_renderer->ChangeViewport(0, 0, x.width, x.height, x.mainViewport); m_renderer->SetCamera(x.rendererCamera); } } void CRenderViewport::PreWidgetRendering() { // if we have not already set the render context for the viewport, do it now // based on the current state of the renderer/viewport, record the previous // context to restore afterwards if (m_cameraSetForWidgetRenderingCount == 0) { m_preWidgetContext = SetCurrentContext(); } // keep track of how many times we've attempted to update the context m_cameraSetForWidgetRenderingCount++; } void CRenderViewport::PostWidgetRendering() { if (m_cameraSetForWidgetRenderingCount > 0) { m_cameraSetForWidgetRenderingCount--; // unwinding - when the viewport context is no longer required, // restore the previous context when widget rendering first began if (m_cameraSetForWidgetRenderingCount == 0) { RestorePreviousContext(m_preWidgetContext); } } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnCameraFOVVariableChanged([[maybe_unused]] IVariable* var) { if (m_viewPane) { m_viewPane->OnFOVChanged(GetFOV()); } } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::HideCursor() { if (m_bCursorHidden || !gSettings.viewports.bHideMouseCursorWhenCaptured) { return; } qApp->setOverrideCursor(Qt::BlankCursor); #if AZ_TRAIT_OS_PLATFORM_APPLE StartFixedCursorMode(this); #endif m_bCursorHidden = true; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::ShowCursor() { if (!m_bCursorHidden || !gSettings.viewports.bHideMouseCursorWhenCaptured) { return; } #if AZ_TRAIT_OS_PLATFORM_APPLE StopFixedCursorMode(); #endif qApp->restoreOverrideCursor(); m_bCursorHidden = false; } bool CRenderViewport::IsKeyDown(Qt::Key key) const { return m_keyDown.contains(key); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::PushDisableRendering() { assert(m_disableRenderingCount >= 0); ++m_disableRenderingCount; } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::PopDisableRendering() { assert(m_disableRenderingCount >= 1); --m_disableRenderingCount; } ////////////////////////////////////////////////////////////////////////// bool CRenderViewport::IsRenderingDisabled() const { return m_disableRenderingCount > 0; } ////////////////////////////////////////////////////////////////////////// QPoint CRenderViewport::WidgetToViewport(const QPoint &point) const { return point * WidgetToViewportFactor(); } QPoint CRenderViewport::ViewportToWidget(const QPoint &point) const { return point / WidgetToViewportFactor(); } ////////////////////////////////////////////////////////////////////////// QSize CRenderViewport::WidgetToViewport(const QSize &size) const { return size * WidgetToViewportFactor(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::BeginUndoTransaction() { PushDisableRendering(); } ////////////////////////////////////////////////////////////////////////// void CRenderViewport::EndUndoTransaction() { PopDisableRendering(); Update(); } void CRenderViewport::UpdateCurrentMousePos(const QPoint& newPosition) { m_prevMousePos = m_mousePos; m_mousePos = newPosition; } void CRenderViewport::BuildDragDropContext(AzQtComponents::ViewportDragContext& context, const QPoint& pt) { const auto scaledPoint = WidgetToViewport(pt); QtViewport::BuildDragDropContext(context, scaledPoint); } void* CRenderViewport::GetSystemCursorConstraintWindow() const { AzFramework::SystemCursorState systemCursorState = AzFramework::SystemCursorState::Unknown; AzFramework::InputSystemCursorRequestBus::EventResult( systemCursorState, AzFramework::InputDeviceMouse::Id, &AzFramework::InputSystemCursorRequests::GetSystemCursorState); const bool systemCursorConstrained = (systemCursorState == AzFramework::SystemCursorState::ConstrainedAndHidden || systemCursorState == AzFramework::SystemCursorState::ConstrainedAndVisible); return systemCursorConstrained ? renderOverlayHWND() : nullptr; } void CRenderViewport::RestoreViewportAfterGameMode() { Matrix34 preGameModeViewTM = m_preGameModeViewTM; QString text = QString( tr("When leaving \" Game Mode \" the engine will automatically restore your camera position to the default position before you " "had entered Game mode.

If you dislike this setting you can always change this anytime in the global " "preferences.

")) .arg(EditorPreferencesGeneralRestoreViewportCameraSettingName); QString restoreOnExitGameModePopupDisabledRegKey("Editor/AutoHide/ViewportCameraRestoreOnExitGameMode"); // Read the popup disabled registry value QSettings settings; QVariant restoreOnExitGameModePopupDisabledRegValue = settings.value(restoreOnExitGameModePopupDisabledRegKey); // Has the user previously disabled being asked about restoring the camera on exiting game mode? if (restoreOnExitGameModePopupDisabledRegValue.isNull()) { // No, ask them now QMessageBox messageBox(QMessageBox::Question, "O3DE", text, QMessageBox::StandardButtons(QMessageBox::No | QMessageBox::Yes), this); messageBox.setDefaultButton(QMessageBox::Yes); QCheckBox* checkBox = new QCheckBox(QStringLiteral("Do not show this message again")); checkBox->setChecked(true); messageBox.setCheckBox(checkBox); // Unconstrain the system cursor and make it visible before we show the dialog box, otherwise the user can't see the cursor. AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id, &AzFramework::InputSystemCursorRequests::SetSystemCursorState, AzFramework::SystemCursorState::UnconstrainedAndVisible); int response = messageBox.exec(); if (checkBox->isChecked()) { settings.setValue(restoreOnExitGameModePopupDisabledRegKey, response); } // Update the value only if the popup hasn't previously been disabled and the value has changed bool newSetting = (response == QMessageBox::Yes); if (newSetting != GetIEditor()->GetEditorSettings()->restoreViewportCamera) { GetIEditor()->GetEditorSettings()->restoreViewportCamera = newSetting; GetIEditor()->GetEditorSettings()->Save(); } } bool restoreViewportCamera = GetIEditor()->GetEditorSettings()->restoreViewportCamera; if (restoreViewportCamera) { SetViewTM(preGameModeViewTM); } else { SetViewTM(m_gameTM); } } #include