/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "AnimationContext.h" #include "IMovieSystem.h" #include "ITimer.h" #include "GameEngine.h" #include "Objects/SelectionGroup.h" #include "Objects/EntityObject.h" #include "TrackView/TrackViewSequence.h" #include "TrackView/TrackViewDialog.h" #include "TrackView/CommentNodeAnimator.h" #include "Maestro/Types/SequenceType.h" #include "RenderViewport.h" #include "Viewport.h" #include "ViewManager.h" #include "IPostRenderer.h" #include ////////////////////////////////////////////////////////////////////////// // Movie Callback. ////////////////////////////////////////////////////////////////////////// class CMovieCallback : public IMovieCallback { protected: virtual void OnMovieCallback(ECallbackReason reason, IAnimNode* pNode) { switch (reason) { case CBR_CHANGENODE: // Invalidate nodes break; case CBR_CHANGETRACK: { // Invalidate tracks CTrackViewDialog* pTrackViewDialog = CTrackViewDialog::GetCurrentInstance(); if (pTrackViewDialog) { pTrackViewDialog->InvalidateDopeSheet(); } } break; } } void OnSetCamera(const SCameraParams& Params) { // Only switch camera when in Play mode. CAnimationContext* ac = GetIEditor()->GetAnimation(); GUID camObjId = GUID_NULL; if (Params.cameraEntityId.IsValid()) { // Find owner editor entity. CEntityObject* pEditorEntity = CEntityObject::FindFromEntityId(Params.cameraEntityId); if (pEditorEntity) { camObjId = pEditorEntity->GetId(); } CViewport* pViewport = GetIEditor()->GetViewManager()->GetSelectedViewport(); if (CRenderViewport* rvp = viewport_cast(pViewport)) { if (!rvp->IsSequenceCamera()) { return; } } } // Switch camera in active rendering view. if (GetIEditor()->GetViewManager()) { GetIEditor()->GetViewManager()->SetCameraObjectId(camObjId); } }; bool IsSequenceCamUsed() const { if (gEnv->IsEditorGameMode() == true) { return true; } if (GetIEditor()->GetViewManager() == NULL) { return false; } CViewport* pRendView = GetIEditor()->GetViewManager()->GetViewport(ET_ViewportCamera); if (pRendView) { return pRendView->IsSequenceCamera(); } return false; } }; static CMovieCallback s_movieCallback; ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- //! class CAnimationContextPostRender : public IPostRenderer { public: CAnimationContextPostRender(CAnimationContext* pAC) : m_pAC(pAC){} void OnPostRender() const { assert(m_pAC); m_pAC->OnPostRender(); } protected: CAnimationContext* m_pAC; }; ////////////////////////////////////////////////////////////////////////// CAnimationContext::CAnimationContext() { m_paused = 0; m_playing = false; m_recording = false; m_bSavedRecordingState = false; m_timeRange.Set(0, 0); m_timeMarker.Set(0, 0); m_currTime = 0.0f; m_lastTimeChangedNotificationTime = .0f; m_bForceUpdateInNextFrame = false; m_fTimeScale = 1.0f; m_pSequence = nullptr; m_mostRecentSequenceId.SetInvalid(); m_mostRecentSequenceTime = 0.0f; m_bLooping = false; m_bAutoRecording = false; m_fRecordingTimeStep = 0; m_bSingleFrame = false; m_bPostRenderRegistered = false; m_bForcingAnimation = false; GetIEditor()->GetUndoManager()->AddListener(this); GetIEditor()->GetSequenceManager()->AddListener(this); GetIEditor()->RegisterNotifyListener(this); } ////////////////////////////////////////////////////////////////////////// CAnimationContext::~CAnimationContext() { GetIEditor()->GetSequenceManager()->RemoveListener(this); GetIEditor()->GetUndoManager()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::Init() { gEnv->pMovieSystem->SetCallback(&s_movieCallback); REGISTER_COMMAND("mov_goToFrameEditor", (ConsoleCommandFunc)GoToFrameCmd, 0, "Make a specified sequence go to a given frame time in the editor."); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::AddListener(IAnimationContextListener* pListener) { stl::push_back_unique(m_contextListeners, pListener); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::RemoveListener(IAnimationContextListener* pListener) { stl::find_and_erase(m_contextListeners, pListener); } void CAnimationContext::NotifyTimeChangedListenersUsingCurrTime() const { for (size_t i = 0; i < m_contextListeners.size(); ++i) { m_contextListeners[i]->OnTimeChanged(m_currTime); } m_lastTimeChangedNotificationTime = m_currTime; } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::SetSequence(CTrackViewSequence* sequence, bool force, bool noNotify, bool user) { float newSeqStartTime = .0f; CTrackViewSequence* pCurrentSequence = m_pSequence; if (!force && sequence == pCurrentSequence) { return; } // Prevent keys being created from time change const bool bRecording = m_recording; m_recording = false; SetRecordingInternal(false); if (sequence) { newSeqStartTime = sequence->GetTimeRange().start; } m_currTime = newSeqStartTime; m_fRecordingCurrTime = newSeqStartTime; if (!m_bPostRenderRegistered) { if (GetIEditor() && GetIEditor()->GetViewManager()) { CViewport* pViewport = GetIEditor()->GetViewManager()->GetViewport(ET_ViewportCamera); if (pViewport) { pViewport->AddPostRenderer(new CAnimationContextPostRender(this)); m_bPostRenderRegistered = true; } } } if (m_pSequence) { m_pSequence->Deactivate(); if (m_playing) { m_pSequence->EndCutScene(); } m_pSequence->UnBindFromEditorObjects(); } m_pSequence = sequence; // Notify a new sequence was just selected. Maestro::EditorSequenceNotificationBus::Broadcast(&Maestro::EditorSequenceNotificationBus::Events::OnSequenceSelected, m_pSequence ? m_pSequence->GetSequenceComponentEntityId() : AZ::EntityId()); if (m_pSequence) { // Set the last valid sequence that was selected. m_mostRecentSequenceId = m_pSequence->GetSequenceComponentEntityId(); if (m_playing) { m_pSequence->BeginCutScene(true); } m_timeRange = m_pSequence->GetTimeRange(); m_timeMarker = m_timeRange; m_pSequence->Activate(); m_pSequence->PrecacheData(newSeqStartTime); m_pSequence->BindToEditorObjects(); } else if (user) { // If this was a sequence that was selected by the user in Track View // and it was "No Sequence" clear the m_mostRecentSequenceId so the sequence // will not be reselected at unwanted events like a slice reload or an undo operation. m_mostRecentSequenceId.SetInvalid(); } ForceAnimation(); if (!noNotify) { NotifyTimeChangedListenersUsingCurrTime(); for (size_t i = 0; i < m_contextListeners.size(); ++i) { m_contextListeners[i]->OnSequenceChanged(m_pSequence); } } TimeChanged(newSeqStartTime); m_recording = bRecording; SetRecordingInternal(bRecording); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::UpdateTimeRange() { if (m_pSequence) { m_timeRange = m_pSequence->GetTimeRange(); // reset the current time to make sure it is clamped // to the new range. SetTime(m_currTime); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::SetTime(float t) { if (t < m_timeRange.start) { t = m_timeRange.start; } if (t > m_timeRange.end) { t = m_timeRange.end; } if (fabs(m_currTime - t) < 0.001f) { return; } m_currTime = t; m_fRecordingCurrTime = t; ForceAnimation(); UpdateAnimatedLights(); NotifyTimeChangedListenersUsingCurrTime(); } void CAnimationContext::TimeChanged(float newTime) { if (m_pSequence) { m_mostRecentSequenceTime = newTime; m_pSequence->TimeChanged(newTime); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::OnSequenceActivated(AZ::EntityId entityId) { // If nothing is selected and there is a valid most recent selected // try to find that sequence by id and select it. This is useful // for restoring the selected sequence during undo and redo. if (m_pSequence == nullptr && m_mostRecentSequenceId.IsValid()) { if (entityId == m_mostRecentSequenceId) { auto editor = GetIEditor(); if (editor != nullptr) { auto manager = editor->GetSequenceManager(); if (manager != nullptr) { auto sequence = manager->GetSequenceByEntityId(m_mostRecentSequenceId); if (sequence != nullptr) { // Hang onto this because SetSequence() will reset it. float lastTime = m_mostRecentSequenceTime; SetSequence(sequence, false, false); // Restore the current time. SetTime(lastTime); // Notify time may have changed, use m_currTime incase it was clamped by SetTime() TimeChanged(m_currTime); } } } } } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::Pause() { assert(m_paused >= 0); m_paused++; if (m_recording) { SetRecordingInternal(false); } GetIEditor()->GetMovieSystem()->Pause(); if (m_pSequence) { m_pSequence->Pause(); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::Resume() { assert(m_paused > 0); m_paused--; if (m_recording && m_paused == 0) { SetRecordingInternal(true); } GetIEditor()->GetMovieSystem()->Resume(); if (m_pSequence) { m_pSequence->Resume(); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::SetRecording(bool recording) { if (recording == m_recording) { return; } m_paused = 0; m_recording = recording; m_playing = false; if (!recording && m_fRecordingTimeStep != 0) { SetAutoRecording(false, 0); } // If started recording, assume we have modified the document. GetIEditor()->SetModifiedFlag(); SetRecordingInternal(recording); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CAnimationContext::SetPlaying(bool playing) { if (playing == m_playing) { return; } m_paused = 0; m_playing = playing; m_recording = false; SetRecordingInternal(false); if (playing) { IMovieSystem* pMovieSystem = GetIEditor()->GetMovieSystem(); pMovieSystem->Resume(); if (m_pSequence) { m_pSequence->Resume(); IMovieUser* pMovieUser = pMovieSystem->GetUser(); if (pMovieUser) { m_pSequence->BeginCutScene(true); } } pMovieSystem->ResumeCutScenes(); } else { IMovieSystem* pMovieSystem = GetIEditor()->GetMovieSystem(); pMovieSystem->Pause(); if (m_pSequence) { m_pSequence->Pause(); } pMovieSystem->PauseCutScenes(); if (m_pSequence) { IMovieUser* pMovieUser = pMovieSystem->GetUser(); if (pMovieUser) { m_pSequence->EndCutScene(); } } } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::Update() { if (m_bForceUpdateInNextFrame) { ForceAnimation(); m_bForceUpdateInNextFrame = false; } // If looking through camera object and recording animation, do not allow camera shake if ((GetIEditor()->GetViewManager()->GetCameraObjectId() != GUID_NULL) && GetIEditor()->GetAnimation()->IsRecording()) { GetIEditor()->GetMovieSystem()->EnableCameraShake(false); } else { GetIEditor()->GetMovieSystem()->EnableCameraShake(true); } if (m_paused > 0 || !(m_playing || m_bAutoRecording)) { if (m_pSequence) { m_pSequence->StillUpdate(); } if (!m_recording) { GetIEditor()->GetMovieSystem()->StillUpdate(); } return; } ITimer* pTimer = GetIEditor()->GetSystem()->GetITimer(); if (!m_bAutoRecording) { AnimateActiveSequence(); float dt = pTimer->GetFrameTime(); m_currTime += dt * m_fTimeScale; if (!m_recording) { GetIEditor()->GetMovieSystem()->PreUpdate(dt); GetIEditor()->GetMovieSystem()->PostUpdate(dt); } } else { float dt = pTimer->GetFrameTime(); m_fRecordingCurrTime += dt * m_fTimeScale; if (fabs(m_fRecordingCurrTime - m_currTime) > m_fRecordingTimeStep) { m_currTime += m_fRecordingTimeStep; } } if (m_currTime > m_timeMarker.end) { if (m_bAutoRecording) { SetAutoRecording(false, 0); } else { if (m_bLooping) { m_currTime = m_timeMarker.start; if (m_pSequence) { m_pSequence->OnLoop(); } } else { SetPlaying(false); m_currTime = m_timeMarker.end; } } } if (m_bAutoRecording) { // This is auto recording mode. // Send sync with physics event to all selected entities. GetIEditor()->GetSelection()->SendEvent(EVENT_PHYSICS_GETSTATE); } if (fabs(m_lastTimeChangedNotificationTime - m_currTime) > 0.001f) { NotifyTimeChangedListenersUsingCurrTime(); } UpdateAnimatedLights(); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::ForceAnimation() { if (m_bForcingAnimation) { // reentrant calls are possible when using subsequences return; } m_bForcingAnimation = true; // Before animating node, pause recording. if (m_bAutoRecording) { Pause(); } AnimateActiveSequence(); // Animate a second time to properly update camera DoF AnimateActiveSequence(); if (m_bAutoRecording) { Resume(); } m_bForcingAnimation = false; } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::SetAutoRecording(bool bEnable, float fTimeStep) { if (bEnable) { m_bAutoRecording = true; m_fRecordingTimeStep = fTimeStep; SetRecording(bEnable); } else { m_bAutoRecording = false; m_fRecordingTimeStep = 0; } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::GoToFrameCmd(IConsoleCmdArgs* pArgs) { if (pArgs->GetArgCount() < 2) { gEnv->pLog->LogError("GoToFrame: You must provide a 'frame time' to go to"); return; } assert(GetIEditor()->GetAnimation()); CTrackViewSequence* pSeq = GetIEditor()->GetAnimation()->GetSequence(); if (!pSeq) { gEnv->pLog->LogError("GoToFrame: No active animation sequence"); return; } float targetFrame = (float)atof(pArgs->GetArg(1)); if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end) { gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end); return; } GetIEditor()->GetAnimation()->m_currTime = targetFrame; GetIEditor()->GetAnimation()->m_bSingleFrame = true; GetIEditor()->GetAnimation()->ForceAnimation(); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::OnPostRender() { if (m_pSequence) { SAnimContext ac; ac.dt = 0; ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate(); ac.time = m_currTime; ac.singleFrame = true; ac.forcePlay = true; m_pSequence->Render(ac); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::UpdateAnimatedLights() { bool bLightAnimationSetActive = m_pSequence && (m_pSequence->GetFlags() & IAnimSequence::eSeqFlags_LightAnimationSet); if (bLightAnimationSetActive == false) { return; } std::vector entityObjects; GetIEditor()->GetObjectManager()->FindObjectsOfType(&CEntityObject::staticMetaObject, entityObjects); std::for_each(std::begin(entityObjects), std::end(entityObjects), [this](CBaseObject* pBaseObject) { CEntityObject* pEntityObject = static_cast(pBaseObject); bool bLight = pEntityObject && pEntityObject->GetEntityClass().compare("Light") == 0; if (bLight) { bool bTimeScrubbing = pEntityObject->GetEntityPropertyBool("bTimeScrubbingInTrackView"); if (bTimeScrubbing) { pEntityObject->SetEntityPropertyFloat("_fTimeScrubbed", m_currTime); } } }); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::BeginUndoTransaction() { m_bSavedRecordingState = m_recording; SetRecordingInternal(false); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::EndUndoTransaction() { if (m_pSequence) { m_pSequence->BindToEditorObjects(); } SetRecordingInternal(m_bSavedRecordingState); } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::TogglePlay() { if (!IsPlaying()) { SetPlaying(true); } else { SetPlaying(false); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::OnSequenceRemoved(CTrackViewSequence* pSequence) { if (m_pSequence == pSequence) { SetSequence(nullptr, true, false); } } ////////////////////////////////////////////////////////////////////////// void CAnimationContext::OnEditorNotifyEvent(EEditorNotifyEvent event) { switch (event) { case eNotify_OnBeginGameMode: if (m_pSequence) { m_pSequence->Resume(); } case eNotify_OnBeginSceneSave: case eNotify_OnBeginLayerExport: 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); break; case eNotify_OnEndGameMode: case eNotify_OnEndSceneSave: case eNotify_OnEndLayerExport: m_currTime = m_sequenceRestoreTime; SetSequence(GetIEditor()->GetSequenceManager()->GetSequenceByEntityId(m_sequenceToRestore), true, true); SetTime(m_sequenceRestoreTime); SetRecordingInternal(m_bSavedRecordingState); break; case eNotify_OnQuit: case eNotify_OnCloseScene: SetSequence(nullptr, true, false); break; case eNotify_OnBeginNewScene: SetSequence(nullptr, false, false); break; case eNotify_OnBeginLoad: m_mostRecentSequenceId.SetInvalid(); m_mostRecentSequenceTime = 0.0f; m_bSavedRecordingState = m_recording; SetRecordingInternal(false); GetIEditor()->GetAnimation()->SetSequence(nullptr, false, false); break; case eNotify_OnEndLoad: SetRecordingInternal(m_bSavedRecordingState); break; } } void CAnimationContext::SetRecordingInternal(bool enableRecording) { GetIEditor()->GetMovieSystem()->SetRecording(enableRecording); if (m_pSequence) { m_pSequence->SetRecording(enableRecording); } } void CAnimationContext::AnimateActiveSequence() { if (!m_pSequence) { return; } SAnimContext ac; ac.dt = 0; ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate(); ac.time = m_currTime; ac.singleFrame = true; ac.forcePlay = true; CViewport* pViewport = GetIEditor()->GetActiveView(); if (CRenderViewport* pRenderViewport = viewport_cast(pViewport)) { CEntityObject* pCameraObject = static_cast(pRenderViewport->GetCameraObject()); ac.m_activeCameraEntity = pCameraObject ? pCameraObject->GetEntityId() : 0; } m_pSequence->Animate(ac); m_pSequence->SyncToConsole(ac); }