Bläddra i källkod

Track View: Fix issues #2833 and #4590, - add switching cameras in the Editor Viewport according to a Director track keys setup. (#18560)

* Add handling for SequenceComponentNotificationBus:OnCameraChanged:

thus switching camera in Editor Viewport Widget, and then reverting camera in Editor Viewport Widget to the initial one.

Signed-off-by: Aleks Starykh <[email protected]>

* Update CTrackViewDialog

1. Updates CTrackViewDialog::OnStopHardReset() to  switch camera in Editor Viewport Widget back to to the initial one.
2.  Improves opening of the of the CTrackViewDialog code path to be more user-friendly.

Signed-off-by: Aleks Starykh <[email protected]>

* Update TrackViewNewSequenceDialog.cpp

Improves entering of a new sequence name, making it more user-friendly

Signed-off-by: Aleks Starykh <[email protected]>

* Remove obsolete code in the CCryEditApp ...

.. as a follow-up to the merged PR #2840, see
https://github.com/o3de/o3de/pull/2840/files#r683305698

Signed-off-by: Aleks Starykh <[email protected]>

* Remove ambiguous enum and member ...

.. as a follow-up to the merged PR #2840, see https://github.com/o3de/o3de/pull/2840/files#r683510637

* Fix adding / destroying sequences and camera name displayed

+ evaluate the corner case with Director Camera track key at 0.0f

Signed-off-by: Aleks Starykh <[email protected]>

* DCO Remediation Commit for Aleks Starykh <[email protected]>

I, Aleks Starykh <[email protected]>, hereby add my Signed-off-by to this commit: b32c8e9c8ee5653e52a0affb58452fb65ae0151f

Signed-off-by: Aleks Starykh <[email protected]>

* Address PR comments

Signed-off-by: Aleks Starykh <[email protected]>

* Evaluate the corner case: a level is reloaded with open TrackVew dialogue

Signed-off-by: Aleks Starykh <[email protected]>

* Disable Editor Viewport Widget camera switching when "Autostart" property for sequence is deselected

Signed-off-by: Aleks Starykh <[email protected]>

* Switch camera in an active Editor Viewport Widget  only in editing mode

Signed-off-by: Aleks Starykh <[email protected]>

* Corner case: Restore camera in the active Editor Viewport, when  the "Autostart" property is cleared

Signed-off-by: Aleks Starykh <[email protected]>

* Fix restoring Editor Viewport state after exiting Game mode and returning back to Editing

Signed-off-by: Aleks Starykh <[email protected]>

---------

Signed-off-by: Aleks Starykh <[email protected]>
Aleks Starykh 8 månader sedan
förälder
incheckning
f307aea1a9

+ 172 - 18
Code/Editor/AnimationContext.cpp

@@ -21,6 +21,8 @@
 #include <AzCore/Serialization/Locale.h>
 #include <AzCore/Time/ITime.h>
 
+#include <AzToolsFramework/API/EditorCameraBus.h>
+
 //////////////////////////////////////////////////////////////////////////
 // Movie Callback.
 //////////////////////////////////////////////////////////////////////////
@@ -120,6 +122,10 @@ CAnimationContext::CAnimationContext()
 //////////////////////////////////////////////////////////////////////////
 CAnimationContext::~CAnimationContext()
 {
+    if (Maestro::SequenceComponentNotificationBus::Handler::BusIsConnected())
+    {
+        Maestro::SequenceComponentNotificationBus::Handler::BusDisconnect();
+    }
     AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
     GetIEditor()->GetSequenceManager()->RemoveListener(this);
     GetIEditor()->GetUndoManager()->RemoveListener(this);
@@ -169,6 +175,12 @@ void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bo
         return;
     }
 
+    if (!m_bIsInGameMode) // In Editor Play Game mode switching Editor Viewport cameras is currently not supported.
+    {
+        // Restore camera in the active Editor Viewport Widget to the value saved while a sequence was activated.
+        SwitchEditorViewportCamera(m_defaulViewCameraEntityId);
+    }
+
     // Prevent keys being created from time change
     const bool bRecording = m_recording;
     m_recording = false;
@@ -197,6 +209,12 @@ void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bo
 
     if (m_pSequence)
     {
+        const auto oldSequenceEntityId = m_pSequence->GetSequenceComponentEntityId();
+        if (Maestro::SequenceComponentNotificationBus::Handler::BusIsConnectedId(oldSequenceEntityId))
+        {
+            Maestro::SequenceComponentNotificationBus::Handler::BusDisconnect(oldSequenceEntityId);
+        }
+
         m_pSequence->Deactivate();
         if (m_playing)
         {
@@ -215,6 +233,9 @@ void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bo
         // Set the last valid sequence that was selected.
         m_mostRecentSequenceId = m_pSequence->GetSequenceComponentEntityId();
 
+        // Get ready to handle camera switching in this sequence, if ever, in order to switch camera in Editor Viewport Widget
+        Maestro::SequenceComponentNotificationBus::Handler::BusConnect(m_mostRecentSequenceId);
+
         if (m_playing)
         {
             m_pSequence->BeginCutScene(true);
@@ -253,6 +274,76 @@ void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bo
     SetRecordingInternal(bRecording);
 }
 
+//////////////////////////////////////////////////////////////////////////
+bool CAnimationContext::IsInGameMode() const
+{
+    const auto editor = GetIEditor();
+    const bool isInGame = editor && editor->IsInGameMode();
+    return m_bIsInGameMode || isInGame;
+}
+
+//////////////////////////////////////////////////////////////////////////
+bool CAnimationContext::IsInEditingMode() const
+{
+    const auto editor = GetIEditor();
+    const bool isNotEditing = !editor || editor->IsInConsolewMode() || editor->IsInTestMode() || editor->IsInLevelLoadTestMode() || editor->IsInPreviewMode();
+    return !m_bIsInGameMode && !isNotEditing;
+}
+
+//////////////////////////////////////////////////////////////////////////
+bool CAnimationContext::IsSequenceAutostartFlagOn() const
+{
+    const auto sequence = GetSequence();
+    return sequence && ((sequence->GetFlags() & IAnimSequence::eSeqFlags_PlayOnReset) != 0);
+}
+
+//////////////////////////////////////////////////////////////////////////
+void CAnimationContext::SwitchEditorViewportCamera(const AZ::EntityId& newCameraEntityId)
+{
+    if (!IsInEditingMode())
+    {
+        return; // Camera switching is currently supported in editing mode only.
+    }
+
+    AZ::EntityId currentEditorViewportCamId;
+    Camera::EditorCameraRequestBus::BroadcastResult(currentEditorViewportCamId, &Camera::EditorCameraRequestBus::Events::GetCurrentViewEntityId);
+    if (currentEditorViewportCamId == newCameraEntityId)
+    {
+        return; // Camera in Editor Viewport Widget is already set to the requested value, avoid unneeded actions.
+    }
+
+    Camera::EditorCameraRequestBus::Broadcast(&Camera::EditorCameraRequestBus::Events::SetViewFromEntityPerspective, newCameraEntityId);
+}
+
+//////////////////////////////////////////////////////////////////////////
+void CAnimationContext::OnCameraChanged([[maybe_unused]] const AZ::EntityId&, const AZ::EntityId& newCameraEntityId)
+{
+
+    if (!newCameraEntityId.IsValid())
+    {
+        return; // Only valid camera Ids are sent to the active editor viewport
+    }
+
+    if (!IsInEditingMode())
+    {
+        return; // Camera switching is currently supported in editing mode only.
+    }
+
+    if (!IsSequenceAutostartFlagOn())
+    {
+        return; // The "Autostart" flag is not set for the active sequence.
+    }
+
+    AZ::EntityId currentEditorViewportCamId;
+    Camera::EditorCameraRequestBus::BroadcastResult( currentEditorViewportCamId, &Camera::EditorCameraRequestBus::Events::GetCurrentViewEntityId);
+    if (currentEditorViewportCamId == newCameraEntityId)
+    {
+        return; // Camera in Editor Viewport Widget is already set to the requested value, avoid unneeded actions.
+    }
+
+    Camera::EditorCameraRequestBus::Broadcast(&Camera::EditorCameraRequestBus::Events::SetViewFromEntityPerspective, newCameraEntityId);
+}
+
 //////////////////////////////////////////////////////////////////////////
 void CAnimationContext::UpdateTimeRange()
 {
@@ -327,13 +418,16 @@ void CAnimationContext::OnSequenceActivated(AZ::EntityId entityId)
                         // Restore the current time.
                         SetTime(lastTime);
 
-                        // Notify time may have changed, use m_currTime incase it was clamped by SetTime()
+                        // Notify time may have changed, use m_currTime in case it was clamped by SetTime()
                         TimeChanged(m_currTime);
                     }
                 }
             }
         }
     }
+
+    // Store initial Editor Viewport camera EntityId 
+    Camera::EditorCameraRequestBus::BroadcastResult(m_defaulViewCameraEntityId, &Camera::EditorCameraRequestBus::Events::GetCurrentViewEntityId);
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -460,6 +554,19 @@ void CAnimationContext::SetPlaying(bool playing)
 //////////////////////////////////////////////////////////////////////////
 void CAnimationContext::Update()
 {
+    if (m_countWaitingForExitingGameMode > 0) // Waiting while Editor is exiting Play Game mode ?
+    {
+        if (--m_countWaitingForExitingGameMode == 0)  // The 2nd frame after StopPlayInEditor event sent ?
+        {
+            m_bIsInGameMode = false; // Now Editor Viewport Widget is in the "Editor" state,
+            RestoreSequenceOnEnteringEditMode(); // So restore previously active sequence and camera in Editor Viewport.
+        }
+        else
+        {
+            return; // while the Editor Viewport state goes from "Started" to "Stopping" and finally back to "Editor".
+        }
+    }
+
     if (m_bForceUpdateInNextFrame)
     {
         ForceAnimation();
@@ -683,6 +790,48 @@ void CAnimationContext::OnSequenceRemoved(CTrackViewSequence* pSequence)
         SetSequence(nullptr, true, false);
     }
 }
+
+//////////////////////////////////////////////////////////////////////////
+void CAnimationContext::StoreSequenceOnExitingEditMode(bool isSwitchingToGameMode)
+{
+    // Store currently active Editor Viewport camera EntityId
+    Camera::EditorCameraRequestBus::BroadcastResult(
+        m_viewCameraEntityIdToRestore, &Camera::EditorCameraRequestBus::Events::GetCurrentViewEntityId);
+
+    if (isSwitchingToGameMode)
+    {
+        SwitchEditorViewportCamera(AZ::EntityId()); // Switch Editor Viewport back to the default Editor camera
+        m_bIsInGameMode = true; // and set the flag of Editor being switched into Play Game mode.
+    }
+
+    if (m_pSequence)
+    {
+        m_sequenceToRestore = m_pSequence->GetSequenceComponentEntityId();
+    }
+    else
+    {
+        m_sequenceToRestore.SetInvalid();
+    }
+
+    m_sequenceRestoreTime = GetTime();
+
+    m_bSavedRecordingState = m_recording;
+    SetRecordingInternal(false);
+    SetSequence(nullptr, true, true);
+}
+
+//////////////////////////////////////////////////////////////////////////
+void CAnimationContext::RestoreSequenceOnEnteringEditMode()
+{
+    m_currTime = m_sequenceRestoreTime;
+    SetSequence(GetIEditor()->GetSequenceManager()->GetSequenceByEntityId(m_sequenceToRestore), true, true);
+    SetTime(m_sequenceRestoreTime);
+
+    SetRecordingInternal(m_bSavedRecordingState);
+
+    SwitchEditorViewportCamera(m_viewCameraEntityIdToRestore); // Switch Editor Viewport back to the stored camera, which was active prior to switching to Play Game mode.
+}
+
 //////////////////////////////////////////////////////////////////////////
 void CAnimationContext::OnEditorNotifyEvent(EEditorNotifyEvent event)
 {
@@ -693,32 +842,37 @@ void CAnimationContext::OnEditorNotifyEvent(EEditorNotifyEvent event)
         {
             m_pSequence->Resume();
         }
-    case eNotify_OnBeginSceneSave:
-    case eNotify_OnBeginLayerExport:
-        if (m_pSequence)
         {
-            m_sequenceToRestore = m_pSequence->GetSequenceComponentEntityId();
+            // This notification arrives before even the OnStartPlayInEditorBegin and later OnStartPlayInEditor events
+            // arrive to Editor Views, and thus switching cameras is still available.
+            // So, after storing an active camera Id, rollback the Editor Viewport to default "Editor camera" in order
+            // to help Editor correctly restore viewport state after switching back to Editing mode,
+            // then set the 'm_bIsInGameMode' flag, store an active sequence and drop it.
+            constexpr const bool isSwitchingToGameMode = true;
+            StoreSequenceOnExitingEditMode(isSwitchingToGameMode);
         }
-        else
+        break;
+
+    case eNotify_OnBeginSceneSave:
+    case eNotify_OnBeginLayerExport:
         {
-            m_sequenceToRestore.SetInvalid();
+            constexpr const bool isSwitchingToGameMode = false;
+            // Store active sequence and camera Ids and drop this sequence.
+            StoreSequenceOnExitingEditMode(isSwitchingToGameMode);
         }
-
-        m_sequenceRestoreTime = GetTime();
-
-        m_bSavedRecordingState = m_recording;
-        SetRecordingInternal(false);
-        SetSequence(nullptr, true, true);
         break;
 
     case eNotify_OnEndGameMode:
+        // Delay restoring previously active sequence and Editor Viewport camera, and clearing 'm_bIsInGameMode' flag,
+        // for 2 frames, while Editor Viewport state goes from "Started" to "Stopping" and finally back to "Editor",
+        // and switching cameras is not supported.
+        m_countWaitingForExitingGameMode = 2;
+        break;
+
     case eNotify_OnEndSceneSave:
     case eNotify_OnEndLayerExport:
-        m_currTime = m_sequenceRestoreTime;
-        SetSequence(GetIEditor()->GetSequenceManager()->GetSequenceByEntityId(m_sequenceToRestore), true, true);
-        SetTime(m_sequenceRestoreTime);
-
-        SetRecordingInternal(m_bSavedRecordingState);
+        // Restore previously active sequence and camera in Editor Viewport.
+        RestoreSequenceOnEnteringEditMode();
         break;
 
     case eNotify_OnQuit:

+ 72 - 7
Code/Editor/AnimationContext.h

@@ -17,6 +17,8 @@
 #include <Range.h>
 #include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
 
+#include <CryCommon/Maestro/Bus/SequenceComponentBus.h>
+
 struct IMovieSystem;
 class CTrackViewSequence;
 
@@ -37,6 +39,8 @@ class CAnimationContext
     , public IUndoManagerListener
     , public ITrackViewSequenceManagerListener
     , public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
+    , public Maestro::SequenceComponentNotificationBus::Handler
+
 {
 public:
     //////////////////////////////////////////////////////////////////////////
@@ -133,7 +137,7 @@ public:
     */
     void SetResetTime(float t) {m_resetTime = t; };
 
-    /** Start animation recorduing.
+    /** Start animation recording.
         Automatically stop playing.
         @param recording True to start recording, false to stop.
     */
@@ -184,24 +188,64 @@ public:
     */
     void OnSequenceActivated(AZ::EntityId entityId);
 
+    /**
+     * Returns true if Editor is in the Play Game mode.
+     */
+    bool IsInGameMode() const;
+
+    /**
+     * Returns true if Editor is in the Editing mode.
+     */
+    bool IsInEditingMode() const;
+
+    /**
+     * Returns true if a sequence is active and has "Autostart" (IAnimSequence::eSeqFlags_PlayOnReset) flag set.
+     */
+    bool IsSequenceAutostartFlagOn() const;
+
+    /**
+     * Switch camera, only in Editing mode, in the active Editor Viewport Widget to the newCameraEntityId 
+     * (including invalid Id, which corresponds to the default Editor camera).
+     */
+    void SwitchEditorViewportCamera(const AZ::EntityId& newCameraEntityId);
+
 private:
     static void GoToFrameCmd(IConsoleCmdArgs* pArgs);
 
     void NotifyTimeChangedListenersUsingCurrTime() const;
 
-    virtual void BeginUndoTransaction() override;
-    virtual void EndUndoTransaction() override;
+    //! IUndoManagerListener overrides
+    void BeginUndoTransaction() override;
+    void EndUndoTransaction() override;
 
+    //! PrefabPublicNotificationBus override
     void OnPrefabInstancePropagationEnd() override;
 
-    virtual void OnSequenceRemoved(CTrackViewSequence* pSequence) override;
+    //! ITrackViewSequenceManagerListener override
+    void OnSequenceRemoved(CTrackViewSequence* pSequence) override;
+
+    //! IEditorNotifyListener override
+    void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
 
-    virtual void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
+    /** SequenceComponentNotificationBus override:
+     *  Switches camera Id in the active editor viewport when a Sequence changes camera during playback in Track View,
+     *  only if in Editing mode and if the "Autostart" flag is set in the active sequence. 
+     */
+    void OnCameraChanged([[maybe_unused]] const AZ::EntityId& oldCameraEntityId, const AZ::EntityId& newCameraEntityId) override;
 
     void AnimateActiveSequence();
 
     void SetRecordingInternal(bool enableRecording);
 
+    /**
+     * Store an active sequence when switching from Editing mode to Game mode or Saving mode.
+     * @param isSwitchingToGameMode True if the function is called when switching from Editing mode to Game mode.
+     */
+    void StoreSequenceOnExitingEditMode(bool isSwitchingToGameMode);
+
+    //! Restore a previously active sequence when switching back from Game mode or Saving mode to Editing mode.
+    void RestoreSequenceOnEnteringEditMode();
+
     //! Current time within active animation sequence.
     float m_currTime;
 
@@ -228,11 +272,32 @@ private:
 
     Range m_timeMarker;
 
+    /** An entity ID of a current camera in an active editor viewport, at the moment a sequence is activated,
+     *  or invalid if the default editor camera is used at the moment.
+     *  Used to restore the default editor viewport camera when clearing "Autostart" property or deselecting a sequence.
+     */
+    AZ::EntityId m_defaulViewCameraEntityId = AZ::EntityId();
+
+    //! Id of the active viewport camera to restore when switching back from game mode and saving mode to Editing mode.
+    AZ::EntityId m_viewCameraEntityIdToRestore = AZ::EntityId();
+
+    /** Switched On with EEditorNotifyEvent::eNotify_OnBeginGameMode,
+     *  switched Off - after 2 frames in Update() since receiving EEditorNotifyEvent::eNotify_OnEndGameModein.
+     */
+    bool m_bIsInGameMode = false;
+
+    /** Set to a positive value with EEditorNotifyEvent::eNotify_OnEndGameMode, in order to delay restoring a previously active
+     *  sequence and Editor Viewport camera, and then resetting m_bIsInGameMode. Decreased to 0 in Update().
+     *  Currently skipping 2 frames is needed, as Editor Viewport state goes from "Started" to "Stopping" and finally back to "Editor".
+     */
+    int m_countWaitingForExitingGameMode = 0;
+
     //! Currently active animation sequence.
     CTrackViewSequence* m_pSequence;
 
-    //! Id of latest valid sequence that was selected. Useful for restoring the selected
-    //! sequence after undo has destroyed and recreated it.
+    /** Id of latest valid sequence that was selected. Useful for restoring the selected
+     * sequence after undo has destroyed and recreated it.
+     */
     AZ::EntityId m_mostRecentSequenceId;
 
     //! The current time of the most recent selected sequence. It's very useful to restore this after an undo.

+ 0 - 14
Code/Editor/CryEdit.cpp

@@ -3027,20 +3027,6 @@ void CCryEditApp::OnToolsPreferences()
     dlg.exec();
 }
 
-//////////////////////////////////////////////////////////////////////////
-void CCryEditApp::OnSwitchToDefaultCamera()
-{
-}
-
-//////////////////////////////////////////////////////////////////////////
-void CCryEditApp::OnUpdateSwitchToDefaultCamera(QAction* action)
-{
-    Q_ASSERT(action->isCheckable());
-    {
-        action->setEnabled(false);
-    }
-}
-
 //////////////////////////////////////////////////////////////////////////
 void CCryEditApp::OnSwitchToSequenceCamera()
 {

+ 0 - 2
Code/Editor/CryEdit.h

@@ -355,8 +355,6 @@ private:
     void OnDisplayGotoPosition();
     void OnFileSavelevelresources();
     void OnClearRegistryData();
-    void OnSwitchToDefaultCamera();
-    void OnUpdateSwitchToDefaultCamera(QAction* action);
     void OnSwitchToSequenceCamera();
     void OnUpdateSwitchToSequenceCamera(QAction* action);
     void OnSwitchToSelectedcamera();

+ 27 - 39
Code/Editor/EditorViewportWidget.cpp

@@ -272,7 +272,7 @@ void EditorViewportWidget::resizeEvent(QResizeEvent* event)
 
     // In the case of the default viewport camera, we must re-set the FOV, which also updates the aspect ratio
     // Component cameras hand this themselves
-    if (m_viewSourceType == ViewSourceType::None)
+    if (!m_viewEntityId.IsValid())
     {
         SetFOV(GetFOV());
     }
@@ -544,7 +544,7 @@ void EditorViewportWidget::PostCameraSet()
     // Special case in the editor; if the camera is the default editor camera,
     // notify that the active view changed. In game mode, it is a hard error to not have
     // any cameras on the view stack!
-    if (m_viewSourceType == ViewSourceType::None)
+    if (!m_viewEntityId.IsValid())
     {
         m_sendingOnActiveChanged = true;
         Camera::CameraNotificationBus::Broadcast(&Camera::CameraNotificationBus::Events::OnActiveViewChanged, AZ::EntityId());
@@ -848,7 +848,7 @@ void EditorViewportWidget::SetViewportId(int id)
     m_perspectiveChangeHandler = SandboxEditor::PerspectiveChangedEvent::Handler(
         [this](const float fovRadians)
         {
-            if (m_viewSourceType == ViewSourceType::None)
+            if (!m_viewEntityId.IsValid())
             {
                 if (m_viewPane)
                 {
@@ -1054,7 +1054,7 @@ bool EditorViewportWidget::AddCameraMenuItems(QMenu* menu)
 
     QAction* action = customCameraMenu->addAction("Editor Camera");
     action->setCheckable(true);
-    action->setChecked(m_viewSourceType == ViewSourceType::None);
+    action->setChecked(!m_viewEntityId.IsValid());
     connect(action, &QAction::triggered, this, &EditorViewportWidget::SetDefaultCamera);
 
     AZ::EBusAggregateResults<AZ::EntityId> getCameraResults;
@@ -1070,7 +1070,7 @@ bool EditorViewportWidget::AddCameraMenuItems(QMenu* menu)
         action = new QAction(QString(entityName.c_str()), nullptr);
         additionalCameras.append(action);
         action->setCheckable(true);
-        action->setChecked(m_viewEntityId == entityId && m_viewSourceType == ViewSourceType::CameraComponent);
+        action->setChecked(m_viewEntityId == entityId && m_viewEntityId.IsValid());
         connect(
             action, &QAction::triggered, this,
             [this, entityId](bool isChecked)
@@ -1193,7 +1193,7 @@ const Matrix34& EditorViewportWidget::GetViewTM() const
 AZ::EntityId EditorViewportWidget::GetCurrentViewEntityId()
 {
     // Sanity check that this camera entity ID is actually the camera entity which owns the current active render view
-    if (m_viewSourceType == ViewSourceType::CameraComponent)
+    if (m_viewEntityId.IsValid())
     {
         // Check that the current view is the same view as the view entity view
         AZ::RPI::ViewPtr viewEntityView;
@@ -1483,7 +1483,6 @@ void EditorViewportWidget::OnActiveViewChanged(const AZ::EntityId& viewEntityId)
             "Please report this as a bug.");
 
         m_viewEntityId = viewEntityId;
-        m_viewSourceType = ViewSourceType::CameraComponent;
         AZStd::string entityName;
         AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, viewEntityId);
         SetName(QString("Camera entity: %1").arg(entityName.c_str()));
@@ -1506,7 +1505,6 @@ void EditorViewportWidget::SetDefaultCamera()
     }
 
     m_viewEntityId.SetInvalid();
-    m_viewSourceType = ViewSourceType::None;
     SetName(m_defaultViewName);
 
     // synchronize the configured editor viewport FOV to the default camera
@@ -1543,7 +1541,7 @@ void EditorViewportWidget::SetDefaultCameraNearFar()
 
 void EditorViewportWidget::OnDefaultCameraNearFarChanged()
 {
-    if (m_viewSourceType == ViewSourceType::None)
+    if (!m_viewEntityId.IsValid())
     {
         SetDefaultCameraNearFar();
     }
@@ -1608,7 +1606,7 @@ bool EditorViewportWidget::IsSelectedCamera() const
     AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
         selectedEntityList, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
 
-    if ((m_viewSourceType == ViewSourceType::CameraComponent) && !selectedEntityList.empty() &&
+    if (m_viewEntityId.IsValid() && !selectedEntityList.empty() &&
         AZStd::find(selectedEntityList.begin(), selectedEntityList.end(), m_viewEntityId) != selectedEntityList.end())
     {
         return true;
@@ -1620,39 +1618,29 @@ bool EditorViewportWidget::IsSelectedCamera() const
 //////////////////////////////////////////////////////////////////////////
 void EditorViewportWidget::CycleCamera()
 {
-    // None -> Sequence -> LegacyCamera -> ... LegacyCamera -> CameraComponent -> ... CameraComponent -> None
-    // AZ_Entity has been intentionally left out of the cycle for now.
-    switch (m_viewSourceType)
+    // None (default editor camera) -> 1st CameraComponent (added editor camera component) -> ... next CameraComponent -> ... -> None
+    if (!m_viewEntityId.IsValid())
     {
-    case EditorViewportWidget::ViewSourceType::None:
-        {
-            SetFirstComponentCamera();
-            break;
-        }
-    case EditorViewportWidget::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())
-                {
-                    SetEntityAsCamera(*currentCameraIterator);
-                    break;
-                }
-            }
-            SetDefaultCamera();
-            break;
-        }
-    default:
+        SetFirstComponentCamera(); // None (default editor camera) -> select a first CameraComponent, if any
+        return;
+    }
+
+    // Find the CameraComponent with the valid m_viewEntityId stored, if it still exists.
+    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())
+    {
+        if (++currentCameraIterator != results.values.end()) // Found -> check that a next one exists ... 
         {
-            SetDefaultCamera();
-            break;
+            SetEntityAsCamera(*currentCameraIterator); // ... and then select it.
+            return;
         }
     }
+    // Go back to None (default editor camera) when the CameraComponent with stored m_viewEntityId
+    // is the last one in the list, or was destroyed.
+    SetDefaultCamera();
 }
 
 void EditorViewportWidget::SetViewFromEntityPerspective(const AZ::EntityId& entityId)

+ 0 - 10
Code/Editor/EditorViewportWidget.h

@@ -130,13 +130,6 @@ private:
     ////////////////////////////////////////////////////////////////////////
     // Private types ...
 
-    enum class ViewSourceType
-    {
-        None,
-        CameraComponent,
-        ViewSourceTypesCount,
-    };
-
     enum class PlayInEditorState
     {
         Editor,
@@ -325,9 +318,6 @@ private:
     // The entity ID of the current camera for this viewport, or invalid if the default editor camera
     AZ::EntityId m_viewEntityId;
 
-    // Determines also if the current camera for this viewport is default editor camera
-    ViewSourceType m_viewSourceType = ViewSourceType::None;
-
     // During play game in editor, holds the editor entity ID of the last
     AZ::EntityId m_viewEntityIdCachedForEditMode;
 

+ 68 - 29
Code/Editor/TrackView/TrackViewDialog.cpp

@@ -30,6 +30,7 @@
 #include <QToolButton>
 
 // AzFramework
+#include <AzToolsFramework/API/EditorCameraBus.h>
 #include <AzToolsFramework/API/ViewPaneOptions.h>
 
 // AzQtComponents
@@ -161,8 +162,8 @@ CTrackViewDialog::CTrackViewDialog(QWidget* pParent /*=nullptr*/)
     m_defaultTracksForEntityNode.push_back(AnimParamType::Position);
     m_defaultTracksForEntityNode.push_back(AnimParamType::Rotation);
 
-    OnInitDialog();
     AddDialogListeners();
+    OnInitDialog();
 
     AZ::EntitySystemBus::Handler::BusConnect();
     AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusConnect();
@@ -855,6 +856,9 @@ void CTrackViewDialog::InvalidateDopeSheet()
 //////////////////////////////////////////////////////////////////////////
 void CTrackViewDialog::Update()
 {
+    CAnimationContext* pAnimationContext = GetIEditor()->GetAnimation();
+    bool wasReloading = m_bNeedReloadSequence;
+
     if (m_bNeedReloadSequence || m_needReAddListeners)
     {
         const CTrackViewSequenceManager* pSequenceManager = GetIEditor()->GetSequenceManager();
@@ -863,7 +867,6 @@ void CTrackViewDialog::Update()
         if (m_bNeedReloadSequence)
         {
             m_bNeedReloadSequence = false;
-            CAnimationContext* pAnimationContext = GetIEditor()->GetAnimation();
             pAnimationContext->SetSequence(sequence, true, false);
         }
         if (m_needReAddListeners)
@@ -873,7 +876,15 @@ void CTrackViewDialog::Update()
         }
     }
 
-    CAnimationContext* pAnimationContext = GetIEditor()->GetAnimation();
+    constexpr const auto noMovieCameraName = "Active Camera";
+    const auto sequence = pAnimationContext->GetSequence();
+    if (!sequence)  // Nothing to update ?
+    {
+        m_activeCamStatic->setText(noMovieCameraName);
+        SetCursorPosText(-1.0f);
+        return;
+    }
+
     float fTime = pAnimationContext->GetTime();
 
     if (fTime != m_fLastTime)
@@ -883,32 +894,44 @@ void CTrackViewDialog::Update()
     }
 
     // Display the name of the active camera in the static control, if any.
-    // The active camera node means two conditions:
-    // 1. Sequence camera is currently active.
-    // 2. The camera which owns this node has been set as the current camera by the director node.
-    IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
+    // Having an active camera means at least the following conditions:
+    // 1. The movie system has the Animation Sequence corresponding to the active TrackView Sequence.
+    // 2. The Animation Sequence has an active Director node.
+    // 3. The current Camera parameters in the movie system are valid.
+    // TODO: invalidate the Camera parameters in the movie system when conditions 1 and 2 are not valid.
 
+    bool cameraNameSet = false;
+    IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
     if (movieSystem)
     {
+        const auto animSequence = movieSystem->FindSequenceById(sequence->GetCryMovieId());
+        IAnimNode* activeDirector = animSequence ? animSequence->GetActiveDirector() : nullptr;
+
         AZ::EntityId camId = movieSystem->GetCameraParams().cameraEntityId;
-        if (camId.IsValid())
+        if (camId.IsValid() && activeDirector)
         {
             AZ::Entity* entity = nullptr;
             AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, camId);
             if (entity)
             {
                 m_activeCamStatic->setText(entity->GetName().c_str());
+                cameraNameSet = true;
+
+                // Evaluate the corner case when the sequence is reloaded and the "Autostart" flag is set for the sequence:
+                // prepare to manually scrub this sequence if the "Autostart" flag is set.
+                if (wasReloading && ((animSequence->GetFlags() & IAnimSequence::eSeqFlags_PlayOnReset) != 0))
+                {
+                    // Try to switch camera in the Editor Viewport Widget to the CameraComponent with the EntityId from this key,
+                    // works only in Editing mode (checked in the called method).
+                    pAnimationContext->SwitchEditorViewportCamera(camId);
+                }
             }
-            else
-            {
-                m_activeCamStatic->setText("Active Camera");
-            }
-        }
-        else
-        {
-            m_activeCamStatic->setText("Active Camera");
         }
     }
+    if (!cameraNameSet)
+    {
+        m_activeCamStatic->setText(noMovieCameraName);
+    }
 
     if (m_wndNodesCtrl)
     {
@@ -1048,6 +1071,8 @@ void CTrackViewDialog::ReloadSequences()
 
     if (sequence)
     {
+        // In case a sequence was previously selected in this Editor session, - restore the selection, selecting this in ReloadSequencesComboBox().
+        m_currentSequenceEntityId = sequence->GetSequenceComponentEntityId();
         sequence->UnBindFromEditorObjects();
     }
 
@@ -1060,22 +1085,18 @@ void CTrackViewDialog::ReloadSequences()
 
     ReloadSequencesComboBox();
 
-    if (m_currentSequenceEntityId.IsValid())
+    if (m_currentSequenceEntityId.IsValid()) // A sequence force-selected when reloading the combo box ?
     {
-        CTrackViewSequenceManager* pSequenceManager = GetIEditor()->GetSequenceManager();
-        sequence = pSequenceManager->GetSequenceByEntityId(m_currentSequenceEntityId);
-
-        const float prevTime = pAnimationContext->GetTime();
-        pAnimationContext->SetSequence(sequence, true, true);
-        pAnimationContext->SetTime(prevTime);
+        OnSequenceComboBox(); // Emulate sequence selection to load it into the dialog
+        sequence = pAnimationContext->GetSequence(); // In case a latest sequence created was selected, actualize the pointer.
     }
-    else
+    else // No sequences yet
     {
         pAnimationContext->SetSequence(nullptr, true, false);
         m_sequencesComboBox->setCurrentIndex(0);
     }
 
-    if (sequence)
+    if (sequence && !sequence->IsBoundToEditorObjects())
     {
         sequence->BindToEditorObjects();
     }
@@ -1093,6 +1114,8 @@ void CTrackViewDialog::ReloadSequencesComboBox()
     m_sequencesComboBox->clear();
     m_sequencesComboBox->addItem(QString(s_kNoSequenceComboBoxEntry));
 
+    AZ::EntityId lastSequenceComponentEntityId;
+    int lastIndex = -1;
     {
         CTrackViewSequenceManager* pSequenceManager = GetIEditor()->GetSequenceManager();
         const unsigned int numSequences = pSequenceManager->GetCount();
@@ -1100,21 +1123,36 @@ void CTrackViewDialog::ReloadSequencesComboBox()
         for (unsigned int k = 0; k < numSequences; ++k)
         {
             CTrackViewSequence* sequence = pSequenceManager->GetSequenceByIndex(k);
+            const auto sequenceComponentEntityId = sequence->GetSequenceComponentEntityId();
+            if (!sequenceComponentEntityId.IsValid())
+            {
+                continue;
+            }
+            lastIndex = static_cast<int>(k);
+            lastSequenceComponentEntityId = sequenceComponentEntityId;
             QString entityIdString = GetEntityIdAsString(sequence->GetSequenceComponentEntityId());
             m_sequencesComboBox->addItem(QString::fromUtf8(sequence->GetName().c_str()), entityIdString);
         }
     }
 
-    if (!m_currentSequenceEntityId.IsValid())
+    if (m_currentSequenceEntityId.IsValid())
     {
-        m_sequencesComboBox->setCurrentIndex(0);
+        QString entityIdString = GetEntityIdAsString(m_currentSequenceEntityId);
+        m_sequencesComboBox->setCurrentIndex(m_sequencesComboBox->findData(entityIdString));
+    }
+    else if (lastSequenceComponentEntityId.IsValid())
+    {
+        // Make opening the dialog more user friendly: selecting a sequence probably worked on lately,
+        // as sequences, when created, are pushed to back into corresponding container. 
+        m_currentSequenceEntityId = lastSequenceComponentEntityId;
+        m_sequencesComboBox->setCurrentIndex(lastIndex + 1);
     }
     else
     {
-        QString entityIdString = GetEntityIdAsString(m_currentSequenceEntityId);
-        m_sequencesComboBox->setCurrentIndex(m_sequencesComboBox->findData(entityIdString));
+        m_sequencesComboBox->setCurrentIndex(0);
     }
     m_sequencesComboBox->blockSignals(false);
+    InvalidateSequence();
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -1256,6 +1294,7 @@ void CTrackViewDialog::OnSequenceComboBox()
         const bool noNotify = false;
         const bool user = true;
         animationContext->SetSequence(sequence, force, noNotify, user);
+        InvalidateSequence(); // Force later update.
     }
 }
 

+ 15 - 22
Code/Editor/TrackViewNewSequenceDialog.cpp

@@ -48,29 +48,34 @@ class CTVNewSequenceDialogValidator : public QValidator
         {
             constexpr int MaxInputLength = 160;
 
+            SetEnabled(true);
+            SetToolTip("");
+
             if (input.isEmpty())
             {
-                SetEnabled(false);
-                return QValidator::Acceptable;
+                return QValidator::Acceptable; // Allow further editing
             }
 
-            bool isValid = false;
-            SetEnabled(true);
             if (input.contains('/'))
             {
                 SetToolTip("A sequence name cannot contain a '/' character");
+                return QValidator::Invalid; // Undo this change
             }
+
             if (input.length() > MaxInputLength)
             {
                 SetToolTip(QString("A sequence name cannot exceed %1 characters").arg(MaxInputLength).toStdString().c_str());
+                return QValidator::Invalid; // Undo this change
             }
-            else if (input == LIGHT_ANIMATION_SET_NAME)
+
+            bool isValid = true;
+            if (input == LIGHT_ANIMATION_SET_NAME)
             {
                 SetToolTip("The sequence name " LIGHT_ANIMATION_SET_NAME " is reserved.\nPlease choose a different name");
+                isValid = false;
             }
             else
             {
-                bool isNewName = true;
                 for (unsigned int k = 0; k < GetIEditor()->GetSequenceManager()->GetCount(); ++k)
                 {
                     CTrackViewSequence* pSequence = GetIEditor()->GetSequenceManager()->GetSequenceByIndex(k);
@@ -78,27 +83,15 @@ class CTVNewSequenceDialogValidator : public QValidator
 
                     if (fullname.compare(input, Qt::CaseInsensitive) == 0)
                     {
-                        isNewName = false;
+                        isValid = false;
+                        SetToolTip("Sequence with this name already exists");
                         break;
                     }
                 }
-                if (!isNewName)
-                {
-                    SetToolTip("Sequence with this name already exists");
-                }
-                else
-                {
-                    isValid = true;
-                }
-            }
-
-            if (isValid)
-            {
-                SetToolTip("");
-                return QValidator::Acceptable;
             }
 
-            return QValidator::Invalid;
+            SetEnabled(isValid);  // Disable OK button if input is invalid.
+            return QValidator::Acceptable; // Accept the change and allow further name editing even if input is invalid.
         }
 
     private: