/* * 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 <AzCore/EBus/EBus.h> #include <AzCore/Component/ComponentApplicationBus.h> #include <AzCore/Component/Entity.h> #include <AzToolsFramework/API/ToolsApplicationAPI.h> #include <Maestro/Bus/EditorSequenceComponentBus.h> #include <AzToolsFramework/API/ComponentEntityObjectBus.h> #include <AzToolsFramework/API/EntityCompositionRequestBus.h> #include <AzToolsFramework/Entity/EditorEntityHelpers.h> #include "TrackViewSequenceManager.h" #include "Material/MaterialManager.h" #include "AnimationContext.h" #include "GameEngine.h" #include <Maestro/Types/SequenceType.h> //////////////////////////////////////////////////////////////////////////// CTrackViewSequenceManager::CTrackViewSequenceManager() { GetIEditor()->RegisterNotifyListener(this); GetIEditor()->GetMaterialManager()->AddListener(this); GetIEditor()->GetObjectManager()->AddObjectEventListener(functor(*this, &CTrackViewSequenceManager::OnObjectEvent)); } //////////////////////////////////////////////////////////////////////////// CTrackViewSequenceManager::~CTrackViewSequenceManager() { GetIEditor()->GetObjectManager()->RemoveObjectEventListener(functor(*this, &CTrackViewSequenceManager::OnObjectEvent)); GetIEditor()->GetMaterialManager()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnEditorNotifyEvent(EEditorNotifyEvent event) { switch (event) { case eNotify_OnBeginGameMode: ResumeAllSequences(); break; case eNotify_OnCloseScene: // Fall through case eNotify_OnBeginLoad: m_bUnloadingLevel = true; break; case eNotify_OnEndNewScene: // Fall through case eNotify_OnEndSceneOpen: // Fall through case eNotify_OnEndLoad: // Fall through case eNotify_OnLayerImportEnd: m_bUnloadingLevel = false; SortSequences(); break; } } //////////////////////////////////////////////////////////////////////////// CTrackViewSequence* CTrackViewSequenceManager::GetSequenceByName(QString name) const { for (auto iter = m_sequences.begin(); iter != m_sequences.end(); ++iter) { CTrackViewSequence* sequence = (*iter).get(); if (sequence->GetName() == name) { return sequence; } } return nullptr; } //////////////////////////////////////////////////////////////////////////// CTrackViewSequence* CTrackViewSequenceManager::GetSequenceByEntityId(const AZ::EntityId& entityId) const { for (auto iter = m_sequences.begin(); iter != m_sequences.end(); ++iter) { CTrackViewSequence* sequence = (*iter).get(); if (sequence->GetSequenceComponentEntityId() == entityId) { return sequence; } } return nullptr; } //////////////////////////////////////////////////////////////////////////// CTrackViewSequence* CTrackViewSequenceManager::GetSequenceByAnimSequence(IAnimSequence* pAnimSequence) const { for (auto iter = m_sequences.begin(); iter != m_sequences.end(); ++iter) { CTrackViewSequence* sequence = (*iter).get(); if (sequence->m_pAnimSequence == pAnimSequence) { return sequence; } } return nullptr; } //////////////////////////////////////////////////////////////////////////// CTrackViewSequence* CTrackViewSequenceManager::GetSequenceByIndex(unsigned int index) const { if (index >= m_sequences.size()) { return nullptr; } return m_sequences[index].get(); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::CreateSequence(QString name, SequenceType sequenceType) { CGameEngine* pGameEngine = GetIEditor()->GetGameEngine(); if (!pGameEngine || !pGameEngine->IsLevelLoaded()) { return; } CTrackViewSequence* pExistingSequence = GetSequenceByName(name); if (pExistingSequence) { return; } AzToolsFramework::ScopedUndoBatch undoBatch("Create TrackView Sequence"); // create AZ::Entity at the current center of the viewport, but don't select it // Store the current selection for selection restore after the sequence component is created AzToolsFramework::EntityIdList selectedEntities; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(selectedEntities, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities); AZ::EntityId newEntityId; // initialized with InvalidEntityId EBUS_EVENT_RESULT(newEntityId, AzToolsFramework::EditorRequests::Bus, CreateNewEntity, AZ::EntityId()); if (newEntityId.IsValid()) { // set the entity name AZ::Entity* entity = nullptr; EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, newEntityId); if (entity) { entity->SetName(static_cast<const char*>(name.toUtf8().data())); } // add the SequenceComponent. The SequenceComponent's Init() method will call OnCreateSequenceObject() which will actually create // the sequence and connect it // #TODO LY-21846: Use "SequenceService" to find component, rather than specific component-type. AzToolsFramework::EntityCompositionRequestBus::Broadcast(&AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities, AzToolsFramework::EntityIdList{ newEntityId }, AZ::ComponentTypeList{ "{C02DC0E2-D0F3-488B-B9EE-98E28077EC56}" }); // restore the Editor selection AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::SetSelectedEntities, selectedEntities); undoBatch.MarkEntityDirty(newEntityId); } } //////////////////////////////////////////////////////////////////////////// IAnimSequence* CTrackViewSequenceManager::OnCreateSequenceObject(QString name, bool isLegacySequence, AZ::EntityId entityId) { // Drop legacy sequences on the floor, they are no longer supported. if (isLegacySequence) { GetIEditor()->GetMovieSystem()->LogUserNotificationMsg(AZStd::string::format("Legacy Sequences are no longer supported. Skipping '%s'.", name.toUtf8().data())); return nullptr; } IAnimSequence* sequence = GetIEditor()->GetMovieSystem()->CreateSequence(name.toUtf8().data(), /*bload =*/ false, /*id =*/ 0U, SequenceType::SequenceComponent, entityId); AZ_Assert(sequence, "Failed to create sequence"); AddTrackViewSequence(new CTrackViewSequence(sequence)); return sequence; } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnSequenceActivated(const AZ::EntityId& entityId) { CAnimationContext* pAnimationContext = GetIEditor()->GetAnimation(); if (pAnimationContext != nullptr) { pAnimationContext->OnSequenceActivated(entityId); } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnCreateSequenceComponent(AZStd::intrusive_ptr<IAnimSequence>& sequence) { // Fix up the internal pointers in the sequence to match the deserialized structure sequence->InitPostLoad(); // Add the sequence to the movie system GetIEditor()->GetMovieSystem()->AddSequence(sequence.get()); // Create the TrackView Sequence CTrackViewSequence* newTrackViewSequence = new CTrackViewSequence(sequence); AddTrackViewSequence(newTrackViewSequence); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::AddTrackViewSequence(CTrackViewSequence* sequenceToAdd) { m_sequences.push_back(std::unique_ptr<CTrackViewSequence>(sequenceToAdd)); SortSequences(); OnSequenceAdded(sequenceToAdd); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::DeleteSequence(CTrackViewSequence* sequence) { const int numSequences = m_sequences.size(); for (int sequenceIndex = 0; sequenceIndex < numSequences; ++sequenceIndex) { if (m_sequences[sequenceIndex].get() == sequence) { AzToolsFramework::ScopedUndoBatch undoBatch("Delete TrackView Sequence"); // delete Sequence Component (and entity if there's no other components left on the entity except for the Transform Component) AZ::Entity* entity = nullptr; AZ::EntityId entityId = sequence->m_pAnimSequence->GetSequenceEntityId(); AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId); if (entity) { const AZ::Uuid editorSequenceComponentTypeId(EditorSequenceComponentTypeId); AZ::Component* sequenceComponent = entity->FindComponent(editorSequenceComponentTypeId); if (sequenceComponent) { AZ::ComponentTypeList requiredComponents; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(requiredComponents, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetRequiredComponentTypes); const int numComponentToDeleteEntity = requiredComponents.size() + 1; AZ::Entity::ComponentArrayType entityComponents = entity->GetComponents(); if (entityComponents.size() == numComponentToDeleteEntity) { // if the entity only has required components + 1 (the found sequenceComponent), delete the Entity. No need to start undo here // AzToolsFramework::ToolsApplicationRequests::DeleteEntities will take care of that AzToolsFramework::EntityIdList entitiesToDelete; entitiesToDelete.push_back(entityId); AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::DeleteEntities, entitiesToDelete); } else { // just remove the sequence component from the entity CUndo undo("Delete TrackView Sequence"); AzToolsFramework::EntityCompositionRequestBus::Broadcast(&AzToolsFramework::EntityCompositionRequests::RemoveComponents, AZ::Entity::ComponentArrayType{ sequenceComponent }); } undoBatch.MarkEntityDirty(entityId); } } // sequence was deleted, we can stop searching break; } } } ////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::RenameNode(CTrackViewAnimNode* animNode, const char* newName) const { CBaseObject* baseObj = nullptr; CTrackViewSequence* sequence = animNode->GetSequence(); AZ_Assert(sequence, "Nodes should never have a null sequence."); if (animNode->IsBoundToEditorObjects()) { if (animNode->GetNodeType() == eTVNT_Sequence) { CTrackViewSequence* sequenceNode = static_cast<CTrackViewSequence*>(animNode); AzToolsFramework::ComponentEntityEditorRequestBus::EventResult(baseObj, sequenceNode->GetSequenceComponentEntityId(), &AzToolsFramework::ComponentEntityEditorRequestBus::Events::GetSandboxObject); } else if (animNode->GetNodeType() == eTVNT_AnimNode) { baseObj = animNode->GetNodeEntity(); } } if (baseObj) { AzToolsFramework::ScopedUndoBatch undoBatch("ModifyEntityName"); static_cast<CObjectManager*>(GetIEditor()->GetObjectManager())->ChangeObjectName(baseObj, newName); undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); } else { AzToolsFramework::ScopedUndoBatch undoBatch("Rename TrackView Node"); animNode->SetName(newName); undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); } } void CTrackViewSequenceManager::RemoveSequenceInternal(CTrackViewSequence* sequence) { std::unique_ptr<CTrackViewSequence> storedTrackViewSequence; for (auto iter = m_sequences.begin(); iter != m_sequences.end(); ++iter) { std::unique_ptr<CTrackViewSequence>& currentSequence = *iter; if (currentSequence.get() == sequence) { // Hang onto this until we finish this function. currentSequence.swap(storedTrackViewSequence); // Remove from CryMovie and TrackView m_sequences.erase(iter); IMovieSystem* pMovieSystem = GetIEditor()->GetMovieSystem(); pMovieSystem->RemoveSequence(sequence->m_pAnimSequence.get()); break; } } OnSequenceRemoved(sequence); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnDeleteSequenceEntity(const AZ::EntityId& entityId) { CTrackViewSequence* sequence = GetSequenceByEntityId(entityId); assert(sequence); if (sequence) { const bool bUndoWasSuspended = GetIEditor()->IsUndoSuspended(); bool isDuringUndo = false; AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(isDuringUndo, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::IsDuringUndoRedo); if (bUndoWasSuspended) { GetIEditor()->ResumeUndo(); } RemoveSequenceInternal(sequence); if (bUndoWasSuspended) { GetIEditor()->SuspendUndo(); } } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::SortSequences() { std::stable_sort(m_sequences.begin(), m_sequences.end(), [](const std::unique_ptr<CTrackViewSequence>& a, const std::unique_ptr<CTrackViewSequence>& b) -> bool { QString aName = a.get()->GetName(); QString bName = b.get()->GetName(); return aName < bName; }); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::ResumeAllSequences() { for (auto iter = m_sequences.begin(); iter != m_sequences.end(); ++iter) { CTrackViewSequence* sequence = (*iter).get(); if (sequence) { sequence->Resume(); } } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnSequenceAdded(CTrackViewSequence* sequence) { for (auto iter = m_listeners.begin(); iter != m_listeners.end(); ++iter) { (*iter)->OnSequenceAdded(sequence); } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnSequenceRemoved(CTrackViewSequence* sequence) { for (auto iter = m_listeners.begin(); iter != m_listeners.end(); ++iter) { (*iter)->OnSequenceRemoved(sequence); } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnDataBaseItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event) { if (event != EDataBaseItemEvent::EDB_ITEM_EVENT_ADD) { const uint numSequences = m_sequences.size(); for (uint i = 0; i < numSequences; ++i) { m_sequences[i]->UpdateDynamicParams(); } } } //////////////////////////////////////////////////////////////////////////// CTrackViewAnimNodeBundle CTrackViewSequenceManager::GetAllRelatedAnimNodes(const CEntityObject* entityObject) const { CTrackViewAnimNodeBundle nodeBundle; const uint sequenceCount = GetCount(); for (uint sequenceIndex = 0; sequenceIndex < sequenceCount; ++sequenceIndex) { CTrackViewSequence* sequence = GetSequenceByIndex(sequenceIndex); nodeBundle.AppendAnimNodeBundle(sequence->GetAllOwnedNodes(entityObject)); } return nodeBundle; } //////////////////////////////////////////////////////////////////////////// CTrackViewAnimNode* CTrackViewSequenceManager::GetActiveAnimNode(const CEntityObject* entityObject) const { CTrackViewAnimNodeBundle nodeBundle = GetAllRelatedAnimNodes(entityObject); const uint nodeCount = nodeBundle.GetCount(); for (uint nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) { CTrackViewAnimNode* animNode = nodeBundle.GetNode(nodeIndex); if (animNode->IsActive()) { return animNode; } } return nullptr; } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::OnObjectEvent(CBaseObject* object, int event) { if (event == CBaseObject::ON_PREATTACHED || event == CBaseObject::ON_PREDETACHED || event == CBaseObject::ON_ATTACHED || event == CBaseObject::ON_DETACHED) { HandleAttachmentChange(object, event); } else if (event == CBaseObject::ON_RENAME) { HandleObjectRename(object); } else if (event == CBaseObject::ON_PREDELETE) { HandleObjectPreDelete(object); } } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::HandleAttachmentChange(CBaseObject* object, int event) { // If an object gets attached/detached from its parent we need to update all related anim nodes, otherwise // they will end up very near the origin or very far away from the attached object when animated if (!qobject_cast<CEntityObject*>(object) || object->CheckFlags(OBJFLAG_DELETED)) { return; } CEntityObject* entityObject = static_cast<CEntityObject*>(object); CTrackViewAnimNodeBundle bundle = GetAllRelatedAnimNodes(entityObject); const uint numAffectedAnimNodes = bundle.GetCount(); if (numAffectedAnimNodes == 0) { return; } std::unordered_set<CTrackViewSequence*> affectedSequences; for (uint i = 0; i < numAffectedAnimNodes; ++i) { CTrackViewAnimNode* animNode = bundle.GetNode(i); affectedSequences.insert(animNode->GetSequence()); } CAnimationContext* pAnimationContext = GetIEditor()->GetAnimation(); CTrackViewSequence* pActiveSequence = pAnimationContext->GetSequence(); const float time = pAnimationContext->GetTime(); for (auto iter = affectedSequences.begin(); iter != affectedSequences.end(); ++iter) { CTrackViewSequence* sequence = *iter; pAnimationContext->SetSequence(sequence, true, true); if (sequence == pActiveSequence) { pAnimationContext->SetTime(time); } for (uint i = 0; i < numAffectedAnimNodes; ++i) { CTrackViewAnimNode* pNode = bundle.GetNode(i); if (pNode->GetSequence() == sequence) { if (event == CBaseObject::ON_PREATTACHEDKEEPXFORM || event == CBaseObject::ON_PREDETACHEDKEEPXFORM) { const Matrix34 transform = pNode->GetNodeEntity()->GetWorldTM(); m_prevTransforms.emplace(pNode, transform); } else if (event == CBaseObject::ON_ATTACHED || event == CBaseObject::ON_DETACHED) { auto findIter = m_prevTransforms.find(pNode); if (findIter != m_prevTransforms.end()) { pNode->GetNodeEntity()->SetWorldTM(findIter->second); } } } } } if (event == CBaseObject::ON_ATTACHED || event == CBaseObject::ON_DETACHED) { m_prevTransforms.clear(); } pAnimationContext->SetSequence(pActiveSequence, true, true); pAnimationContext->SetTime(time); } //////////////////////////////////////////////////////////////////////////// void CTrackViewSequenceManager::HandleObjectRename(CBaseObject* object) { CTrackViewAnimNodeBundle bundle; CEntityObject* entityObject = static_cast<CEntityObject*>(object); AZ_Assert(entityObject, "Expected CEntityObject case to succeed.") if (entityObject) { // entity or component entity sequence object bundle = GetAllRelatedAnimNodes(entityObject); // GetAllRelatedAnimNodes only accounts for entities in the sequences, not the sequence entities themselves. We additionally check for sequence // entities that have object as their entity object for renaming const uint sequenceCount = GetCount(); for (uint sequenceIndex = 0; sequenceIndex < sequenceCount; ++sequenceIndex) { CTrackViewSequence* sequence = GetSequenceByIndex(sequenceIndex); CBaseObject* sequenceObject = nullptr; AzToolsFramework::ComponentEntityEditorRequestBus::EventResult(sequenceObject, sequence->GetSequenceComponentEntityId(), &AzToolsFramework::ComponentEntityEditorRequestBus::Events::GetSandboxObject); if (object == sequenceObject) { bundle.AppendAnimNode(sequence); } } } const uint numAffectedNodes = bundle.GetCount(); for (uint i = 0; i < numAffectedNodes; ++i) { CTrackViewAnimNode* animNode = bundle.GetNode(i); animNode->SetName(object->GetName().toUtf8().data()); } if (numAffectedNodes > 0) { GetIEditor()->Notify(eNotify_OnReloadTrackView); } } void CTrackViewSequenceManager::HandleObjectPreDelete(CBaseObject* object) { if (!qobject_cast<CEntityObject*>(object)) { return; } // we handle pre-delete instead of delete because GetAllRelatedAnimNodes() uses the ObjectManager to find node owners CEntityObject* entityObject = static_cast<CEntityObject*>(object); CTrackViewAnimNodeBundle bundle = GetAllRelatedAnimNodes(entityObject); const uint numAffectedAnimNodes = bundle.GetCount(); for (uint i = 0; i < numAffectedAnimNodes; ++i) { CTrackViewAnimNode* animNode = bundle.GetNode(i); animNode->OnEntityRemoved(); } if (numAffectedAnimNodes > 0) { // Only reload trackview if the object being deleted has related anim nodes. GetIEditor()->Notify(eNotify_OnReloadTrackView); } }