text: /*
   * 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 <QPainter>
  #include <QScopedValueRollback>
  #include <QCheckBox>
  #include <QMessageBox>
  #include <QTimer>
  
  // AzCore
  #include <AzCore/Console/Console.h>
  #include <AzCore/Component/EntityId.h>
  #include <AzCore/Interface/Interface.h>
  #include <AzCore/Math/Frustum.h>
  #include <AzCore/Math/VectorConversions.h>
  #include <AzCore/std/sort.h>
  #include <AzCore/Component/ComponentApplicationBus.h>
  
  // AzFramework
  #include <AzFramework/Components/CameraBus.h>
  #include <AzFramework/Viewport/DisplayContextRequestBus.h>
  #include <AzFramework/Terrain/TerrainDataRequestBus.h>
  #if defined(AZ_PLATFORM_WINDOWS)
  #   include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
  #endif // defined(AZ_PLATFORM_WINDOWS)
  #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>                   // for AzFramework::InputDeviceMouse
  
  // AzQtComponents
  #include <AzQtComponents/Utilities/QtWindowUtilities.h>
  
  // AzToolsFramework
  #include <AzToolsFramework/API/ComponentEntityObjectBus.h>
  #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
  #include <AzToolsFramework/Editor/EditorContextMenuBus.h>
  #include <AzToolsFramework/Manipulators/ManipulatorManager.h>
  #include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
  #include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
  
  
  // CryCommon
  #include <CryCommon/HMDBus.h>
  
  // AzFramework
  #include <AzFramework/Render/IntersectorInterface.h>
  
  // 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 <Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h>
  
  // LmbrCentral
  #include <LmbrCentral/Rendering/EditorCameraCorrectionBus.h>
  
  #include <QtGui/private/qhighdpiscaling_p.h>
  
  #include <IEntityRenderState.h>
  #include <IPhysics.h>
  #include <IStatObj.h>
  
  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<EditorEntityNotifications>(*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<QKeyEvent*>(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<bool> 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<CComponentEntityObject*>(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<AZ::EntityId>& 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<AZ::EntityId>& 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<int>(clientAreaSize.m_width), aznumeric_cast<int>(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<CRenderViewport*>(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<AZ::EntityId> 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<QAction*> 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<Vec3> verts;
          static std::vector<ColorB> 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<AZStd::pair<float, AZ::Vector3>> 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<AZStd::pair<float, AZ::Vector3>> 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<float>::max()), Vec3(-std::numeric_limits<float>::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<AZ::EntityId> 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<AZ::EntityId> 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<AZ::EntityId> 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<HWND>(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<float>(newSize.width()) / aznumeric_cast<float>(newWidth);
      float heightScale = aznumeric_cast<float>(newSize.height()) / aznumeric_cast<float>(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.<br/><br/><small>If you dislike this setting you can always change this anytime in the global "
              "preferences.</small><br/><br/>"))
              .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 <moc_RenderViewport.cpp>
  