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.
509 lines
18 KiB
C++
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);
|
|
}
|
|
}
|