You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Editor/TrackView/TrackViewSequenceManager.cpp

509 lines
18 KiB
C++

/*
* 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
*
*/
#include "EditorDefs.h"
#include "TrackViewSequenceManager.h"
// AzToolsFramework
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
// CryCommon
#include <CryCommon/Maestro/Bus/EditorSequenceComponentBus.h>
#include <CryCommon/Maestro/Types/SequenceType.h>
// Editor
#include "AnimationContext.h"
#include "GameEngine.h"
#include "Include/IObjectManager.h"
#include "Objects/ObjectManager.h"
////////////////////////////////////////////////////////////////////////////
CTrackViewSequenceManager::CTrackViewSequenceManager()
{
GetIEditor()->RegisterNotifyListener(this);
AZ::EntitySystemBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////
CTrackViewSequenceManager::~CTrackViewSequenceManager()
{
AZ::EntitySystemBus::Handler::BusDisconnect();
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 (QString::fromUtf8(sequence->GetName().c_str()) == 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, [[maybe_unused]] 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 = static_cast<int>(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 = static_cast<int>(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
{
AZ::EntityId entityId;
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);
entityId = sequenceNode->GetSequenceComponentEntityId();
}
else if (animNode->GetNodeType() == eTVNT_AnimNode)
{
entityId = animNode->GetNodeEntityId();
}
}
if (entityId.IsValid())
{
AzToolsFramework::ScopedUndoBatch undoBatch("ModifyEntityName");
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
entity->SetName(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 = QString::fromUtf8(a.get()->GetName().c_str());
QString bName = QString::fromUtf8(b.get()->GetName().c_str());
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([[maybe_unused]] IDataBaseItem* pItem, EDataBaseItemEvent event)
{
if (event != EDataBaseItemEvent::EDB_ITEM_EVENT_ADD)
{
const size_t numSequences = m_sequences.size();
for (size_t i = 0; i < numSequences; ++i)
{
m_sequences[i]->UpdateDynamicParams();
}
}
}
////////////////////////////////////////////////////////////////////////////
CTrackViewAnimNodeBundle CTrackViewSequenceManager::GetAllRelatedAnimNodes(const AZ::EntityId entityId) const
{
CTrackViewAnimNodeBundle nodeBundle;
const uint sequenceCount = GetCount();
for (uint sequenceIndex = 0; sequenceIndex < sequenceCount; ++sequenceIndex)
{
CTrackViewSequence* sequence = GetSequenceByIndex(sequenceIndex);
nodeBundle.AppendAnimNodeBundle(sequence->GetAllOwnedNodes(entityId));
}
return nodeBundle;
}
////////////////////////////////////////////////////////////////////////////
CTrackViewAnimNode* CTrackViewSequenceManager::GetActiveAnimNode(const AZ::EntityId entityId) const
{
CTrackViewAnimNodeBundle nodeBundle = GetAllRelatedAnimNodes(entityId);
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::OnEntityNameChanged(const AZ::EntityId& entityId, const AZStd::string& name)
{
CTrackViewAnimNodeBundle bundle;
// entity or component entity sequence object
bundle = GetAllRelatedAnimNodes(entityId);
// 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);
if (entityId == sequence->GetSequenceComponentEntityId())
{
bundle.AppendAnimNode(sequence);
}
}
const uint numAffectedNodes = bundle.GetCount();
for (uint i = 0; i < numAffectedNodes; ++i)
{
CTrackViewAnimNode* animNode = bundle.GetNode(i);
animNode->SetName(name.c_str());
}
if (numAffectedNodes > 0)
{
GetIEditor()->Notify(eNotify_OnReloadTrackView);
}
}
void CTrackViewSequenceManager::OnEntityDestruction(const AZ::EntityId& entityId)
{
// we handle pre-delete instead of delete because GetAllRelatedAnimNodes() uses the ObjectManager to find node owners
CTrackViewAnimNodeBundle bundle = GetAllRelatedAnimNodes(entityId);
const uint numAffectedAnimNodes = bundle.GetCount();
for (uint i = 0; i < numAffectedAnimNodes; ++i)
{
CTrackViewAnimNode* animNode = bundle.GetNode(i);
animNode->OnEntityRemoved();
}
if (numAffectedAnimNodes > 0)
{
// Only reload track view if the object being deleted has related anim nodes.
GetIEditor()->Notify(eNotify_OnReloadTrackView);
}
}