text: /*
  * 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.
  
  // Description : Implementation of IAnimSequence interface.
  
  
  #include "Maestro_precompiled.h"
  
  #include <Maestro/Bus/EditorSequenceComponentBus.h>
  
  #include "AnimSequence.h"
  #include "AnimAZEntityNode.h"
  #include "AnimComponentNode.h"
  #include "CVarNode.h"
  #include "ScriptVarNode.h"
  #include "SceneNode.h"
  #include "StlUtils.h"
  #include "MaterialNode.h"
  #include "EventNode.h"
  #include "LayerNode.h"
  #include "CommentNode.h"
  #include "AnimPostFXNode.h"
  #include "AnimScreenFaderNode.h"
  #include "I3DEngine.h"
  #include "ShadowsSetupNode.h"
  #include "AnimEnvironmentNode.h"
  #include "TimeOfDayNode.h"
  #include "SequenceTrack.h"
  #include "AnimNodeGroup.h"
  #include "IScriptSystem.h"
  #include "Components/IComponentRender.h"
  #include <Maestro/Types/AnimNodeType.h>
  #include <Maestro/Types/SequenceType.h>
  #include <Maestro/Types/AnimParamType.h>
  
  //////////////////////////////////////////////////////////////////////////
  CAnimSequence::CAnimSequence(IMovieSystem* pMovieSystem, uint32 id, SequenceType sequenceType)
      : m_refCount(0)
  {
      m_nextGenId = 1;
      m_pMovieSystem = pMovieSystem;
      m_flags = 0;
      m_pParentSequence = NULL;
      m_timeRange.Set(0, 10);
      m_bPaused = false;
      m_bActive = false;
      m_legacySequenceObject = nullptr;
      m_activeDirector = NULL;
      m_activeDirectorNodeId = -1;
      m_precached = false;
      m_bResetting = false;
      m_sequenceType = sequenceType;
      m_time = -FLT_MAX;
      SetId(id);
  
      m_pEventStrings = aznew CAnimStringTable;
      m_expanded = true;
  }
  
  //////////////////////////////////////////////////////////////////////////
  CAnimSequence::CAnimSequence()
      : CAnimSequence((gEnv) ? gEnv->pMovieSystem : nullptr, 0, SequenceType::SequenceComponent)
  {
  }
  
  CAnimSequence::~CAnimSequence()
  {
      // clear reference to me from all my nodes
      for (int i = m_nodes.size(); --i >= 0;)
      {
          if (m_nodes[i])
          {
              m_nodes[i]->SetSequence(nullptr);
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::add_ref()
  {
      ++m_refCount;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::release()
  {
      if (--m_refCount <= 0)
      {
          delete this;
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetName(const char* name)
  {
      if (!m_pMovieSystem)
      {
          return;   // should never happen, null pointer guard
      }
  
      string originalName = GetName();
  
      m_name = name;
      m_pMovieSystem->OnSequenceRenamed(originalName, m_name.c_str());
  
      // the sequence named LIGHT_ANIMATION_SET_NAME is a singleton sequence to hold all light animations.
      if (m_name == LIGHT_ANIMATION_SET_NAME)
      {
          // ensure it stays a singleton. If one already exists, deregister it.
          if (CLightAnimWrapper::GetLightAnimSet())
          {
              CLightAnimWrapper::InvalidateAllNodes();
              CLightAnimWrapper::SetLightAnimSet(0);
          }
  
          CLightAnimWrapper::SetLightAnimSet(this);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  const char* CAnimSequence::GetName() const
  {
      return m_name.c_str();
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::ResetId()
  {
      if (!m_pMovieSystem)
      {
          return;   // should never happen, null pointer guard
      }
  
      SetId(m_pMovieSystem->GrabNextSequenceId());
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetFlags(int flags)
  {
      m_flags = flags;
  }
  
  //////////////////////////////////////////////////////////////////////////
  int CAnimSequence::GetFlags() const
  {
      return m_flags;
  }
  
  //////////////////////////////////////////////////////////////////////////
  int CAnimSequence::GetCutSceneFlags(const bool localFlags) const
  {
      int currentFlags = m_flags & (eSeqFlags_NoHUD | eSeqFlags_NoPlayer | eSeqFlags_NoGameSounds | eSeqFlags_NoAbort);
  
      if (m_pParentSequence != NULL)
      {
          if (localFlags == true)
          {
              currentFlags &= ~m_pParentSequence->GetCutSceneFlags();
          }
          else
          {
              currentFlags |= m_pParentSequence->GetCutSceneFlags();
          }
      }
  
      return currentFlags;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetParentSequence(IAnimSequence* pParentSequence)
  {
      m_pParentSequence = pParentSequence;
  }
  
  //////////////////////////////////////////////////////////////////////////
  const IAnimSequence* CAnimSequence::GetParentSequence() const
  {
      return m_pParentSequence;
  }
  
  //////////////////////////////////////////////////////////////////////////
  int CAnimSequence::GetNodeCount() const
  {
      return m_nodes.size();
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::GetNode(int index) const
  {
      assert(index >= 0 && index < (int)m_nodes.size());
      return m_nodes[index].get();
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::AddNode(IAnimNode* animNode)
  {
      AZ_Assert(animNode, "Expected valid animNode");
      if (!animNode)
      {
          return false;
      }
  
      animNode->SetSequence(this);
      animNode->SetTimeRange(m_timeRange);
  
      AnimNodeType nodeType = animNode->GetType();
  
      //These nodes can fail to be activated normally
      if (nodeType == AnimNodeType::Environment ||
          nodeType >= AnimNodeType::TOD_START && nodeType <= AnimNodeType::TOD_END)
      {
          animNode->Activate(true);
      }
  
      // Check if this node already in sequence. If found, don't add it again.
      bool found = false;
      for (int i = 0; i < (int)m_nodes.size(); i++)
      {
          if (animNode == m_nodes[i].get())
          {
              found = true;
              break;
          }
      }
      if (!found)
      {
          m_nodes.push_back(AZStd::intrusive_ptr<IAnimNode>(animNode));
      }
  
      const int nodeId = static_cast<CAnimNode*>(animNode)->GetId();
      if (nodeId >= (int)m_nextGenId)
      {
          m_nextGenId = nodeId + 1;
      }
  
      // Make sure m_nextTrackId is bigger than the biggest existing track id.
      // m_nextTrackId is not serialized (track id's are) so this code will
      // exercise every time a sequence is loaded.
      int trackCount = animNode->GetTrackCount();
      for (int trackIndex = 0; trackIndex < trackCount; trackIndex++)
      {
          IAnimTrack* track = animNode->GetTrackByIndex(trackIndex);
          AZ_Assert(track, "Expected valid track");
          if (track->GetId() >= m_nextTrackId)
          {
              m_nextTrackId = track->GetId() + 1;
          }
  
          int subTrackCount = track->GetSubTrackCount();
          for (int subTrackIndex = 0; subTrackIndex < subTrackCount; subTrackIndex++)
          {
              IAnimTrack* subTrack = track->GetSubTrack(subTrackIndex);
              AZ_Assert(subTrack, "Expected valid subtrack.");
              if (subTrack->GetId() >= m_nextTrackId)
              {
                  m_nextTrackId = subTrack->GetId() + 1;
              }
          }
      }
  
      if (animNode->NeedToRender())
      {
          AddNodeNeedToRender(animNode);
      }
  
      bool bNewDirectorNode = m_activeDirector == NULL && nodeType == AnimNodeType::Director;
      if (bNewDirectorNode)
      {
          m_activeDirector = animNode;
      }
  
      return true;
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::CreateNodeInternal(AnimNodeType nodeType, uint32 nNodeId)
  {
      if (!m_pMovieSystem)
      {
          return nullptr;   // should never happen, null pointer guard
      }
  
      CAnimNode* animNode = NULL;
  
      if (nNodeId == -1)
      {
          nNodeId = m_nextGenId;
      }
  
      switch (nodeType)
      {
          case AnimNodeType::AzEntity:
              animNode = aznew CAnimAzEntityNode(nNodeId);
              break;
          case AnimNodeType::Component:
              animNode = aznew CAnimComponentNode(nNodeId);
              break;
          case AnimNodeType::CVar:
              animNode = aznew CAnimCVarNode(nNodeId);
              break;
          case AnimNodeType::ScriptVar:
              animNode = aznew CAnimScriptVarNode(nNodeId);
              break;
          case AnimNodeType::Director:
              animNode = aznew CAnimSceneNode(nNodeId);
              break;
          case AnimNodeType::Material:
              animNode = aznew CAnimMaterialNode(nNodeId);
              break;
          case AnimNodeType::Event:
              animNode = aznew CAnimEventNode(nNodeId);
              break;
          case AnimNodeType::Group:
              animNode = aznew CAnimNodeGroup(nNodeId);
              break;
          case  AnimNodeType::Layer:
              animNode = aznew CLayerNode(nNodeId);
              break;
          case AnimNodeType::Comment:
              animNode = aznew CCommentNode(nNodeId);
              break;
          case AnimNodeType::RadialBlur:
          case AnimNodeType::ColorCorrection:
          case AnimNodeType::DepthOfField:
              animNode = CAnimPostFXNode::CreateNode(nNodeId, nodeType);
              break;
          case AnimNodeType::ShadowSetup:
              animNode = aznew CShadowsSetupNode(nNodeId);
              break;
          case AnimNodeType::ScreenFader:
              animNode = aznew CAnimScreenFaderNode(nNodeId);
              break;
          case AnimNodeType::Environment:
              animNode = aznew CAnimEnvironmentNode(nNodeId);
              break;
          case AnimNodeType::TOD_Sun:
          case AnimNodeType::TOD_Fog:
          case AnimNodeType::TOD_VolumetricFog:
          case AnimNodeType::TOD_SkyLight:
          case AnimNodeType::TOD_NightSky:
          case AnimNodeType::TOD_NightSkyMultiplier:
          case AnimNodeType::TOD_CloudShading:
          case AnimNodeType::TOD_SunRaysEffect:
          case AnimNodeType::TOD_AdvancedTOD:
          case AnimNodeType::TOD_Filters:
          case AnimNodeType::TOD_DepthOfField:
          case AnimNodeType::TOD_Shadows:
          case AnimNodeType::TOD_Obsolete:
          case AnimNodeType::TOD_HDR:
              animNode = CAnimTODNode::CreateNode(nNodeId, nodeType);
              break;
          default:
              m_pMovieSystem->LogUserNotificationMsg("AnimNode cannot be added because it is an unsupported object type.");
              break;
      }
  
      if (animNode)
      {
          if (AddNode(animNode))
          {
              // If there isn't an active director, set it now.
              if (m_activeDirector == NULL && animNode->GetType() == AnimNodeType::Director)
              {
                  SetActiveDirector(animNode);
              }
          }
      }
  
      return animNode;
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::CreateNode(AnimNodeType nodeType)
  {
      return CreateNodeInternal(nodeType);
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::CreateNode(XmlNodeRef node)
  {
      if (!GetMovieSystem())
      {
          return 0;   // should never happen, null pointer guard
      }
  
      AnimNodeType type;
      GetMovieSystem()->SerializeNodeType(type, node, true, IAnimSequence::kSequenceVersion, 0);
  
      XmlString name;
      if (!node->getAttr("Name", name))
      {
          return 0;
      }
  
      IAnimNode* pNewNode = CreateNode(type);
      if (!pNewNode)
      {
          return 0;
      }
  
      pNewNode->SetName(name);
      pNewNode->Serialize(node, true, true);
  
      CAnimNode* newAnimNode = static_cast<CAnimNode*>(pNewNode);
  
      // Make sure de-serializing this node didn't just create an id conflict. This can happen sometimes
      // when copy/pasting nodes from a different sequence to this one.
      for (auto curNode : m_nodes)
      {
          CAnimNode* animNode = static_cast<CAnimNode*>(curNode.get());
          if (animNode->GetId() == newAnimNode->GetId() && animNode != newAnimNode)
          {
              // Conflict detected, resolve it by assigning a new id to the new node.
              newAnimNode->SetId(m_nextGenId++);
          }
      }
  
      return pNewNode;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::RemoveNode(IAnimNode* node, bool removeChildRelationships)
  {
      assert(node != 0);
  
      static_cast<CAnimNode*>(node)->Activate(false);
      static_cast<CAnimNode*>(node)->OnReset();
  
      for (int i = 0; i < (int)m_nodes.size(); )
      {
          if (node == m_nodes[i].get())
          {
              m_nodes.erase(m_nodes.begin() + i);
  
              if (node->NeedToRender())
              {
                  RemoveNodeNeedToRender(node);
              }
  
              continue;
          }
          if (removeChildRelationships && m_nodes[i]->GetParent() == node)
          {
              m_nodes[i]->SetParent(0);
          }
  
          i++;
      }
  
      AnimNodeType nodeType = node->GetType();
  
      // The removed one was the active director node.
      if (m_activeDirector == node)
      {
          // Clear the active one.
          m_activeDirector = NULL;
          m_activeDirectorNodeId = -1;
  
          // If there is another director node, set it as active.
          for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
          {
              IAnimNode* pNode = it->get();
              if (pNode->GetType() == AnimNodeType::Director)
              {
                  SetActiveDirector(pNode);
                  break;
              }
          }
      }
  
      // On removing a node, animate all TOD Nodes at the current time to fix any
      // issues with ToD settings incorrectly clearing
      if (nodeType == AnimNodeType::Environment ||
          nodeType >= AnimNodeType::TOD_START && nodeType <= AnimNodeType::TOD_START)
      {
          SAnimContext ec;
          ec.singleFrame = true;
          ec.resetting = false;
          ec.sequence = this;
          ec.time = m_timeRange.start;
          Animate(ec);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::RemoveAll()
  {
      stl::free_container(m_nodes);
      stl::free_container(m_events);
      stl::free_container(m_nodesNeedToRender);
      m_activeDirector = NULL;
      m_activeDirectorNodeId = -1;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Reset(bool bSeekToStart)
  {
      if (GetFlags() & eSeqFlags_LightAnimationSet)
      {
          return;
      }
  
      m_precached = false;
      m_bResetting = true;
  
      if (!bSeekToStart)
      {
          for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
          {
              IAnimNode* animNode = it->get();
              static_cast<CAnimNode*>(animNode)->OnReset();
          }
          m_bResetting = false;
          return;
      }
  
      bool bWasActive = m_bActive;
  
      if (!bWasActive)
      {
          Activate();
      }
  
      SAnimContext ec;
      ec.singleFrame = true;
      ec.resetting = true;
      ec.sequence = this;
      ec.time = m_timeRange.start;
      Animate(ec);
  
      if (!bWasActive)
      {
          Deactivate();
      }
      else
      {
          for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
          {
              IAnimNode* animNode = it->get();
              static_cast<CAnimNode*>(animNode)->OnReset();
          }
      }
  
      m_bResetting = false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::ResetHard()
  {
      if (GetFlags() & eSeqFlags_LightAnimationSet)
      {
          return;
      }
  
      m_bResetting = true;
  
      bool bWasActive = m_bActive;
  
      if (!bWasActive)
      {
          Activate();
      }
  
      SAnimContext ec;
      ec.singleFrame = true;
      ec.resetting = true;
      ec.sequence = this;
      ec.time = m_timeRange.start;
      Animate(ec);
  
      if (!bWasActive)
      {
          Deactivate();
      }
      else
      {
          for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
          {
              IAnimNode* animNode = it->get();
              static_cast<CAnimNode*>(animNode)->OnResetHard();
          }
      }
  
      m_bResetting = false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Pause()
  {
      if (GetFlags() & eSeqFlags_LightAnimationSet || m_bPaused)
      {
          return;
      }
  
      m_bPaused = true;
  
      // Detach animation block from all nodes in this sequence.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->OnPause();
      }
  
      // Notify EBus listeners
      Maestro::SequenceComponentNotificationBus::Event(GetSequenceEntityId(), &Maestro::SequenceComponentNotificationBus::Events::OnPause);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Resume()
  {
      if (GetFlags() & eSeqFlags_LightAnimationSet)
      {
          return;
      }
  
      if (m_bPaused)
      {
          m_bPaused = false;
  
          for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
          {
              IAnimNode* animNode = it->get();
              static_cast<CAnimNode*>(animNode)->OnResume();
          }
  
          // Notify EBus listeners
          Maestro::SequenceComponentNotificationBus::Event(GetSequenceEntityId(), &Maestro::SequenceComponentNotificationBus::Events::OnResume);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::OnLoop()
  {
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->OnLoop();
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::IsPaused() const
  {
      return m_bPaused;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::OnStart()
  {
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->OnStart();
      }
  
      // notify listeners
      Maestro::SequenceComponentNotificationBus::Event(GetSequenceEntityId(), &Maestro::SequenceComponentNotificationBus::Events::OnStart, m_time);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::OnStop()
  {
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->OnStop();
      }
  
      // notify listeners
      Maestro::SequenceComponentNotificationBus::Event(GetSequenceEntityId(), &Maestro::SequenceComponentNotificationBus::Events::OnStop, m_time);
  }
  
  void CAnimSequence::TimeChanged(float newTime)
  {
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          animNode->TimeChanged(newTime);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::StillUpdate()
  {
      if (GetFlags() & eSeqFlags_LightAnimationSet)
      {
          return;
      }
  
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          animNode->StillUpdate();
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Animate(const SAnimContext& ec)
  {
      assert(m_bActive);
  
      if (GetFlags() & eSeqFlags_LightAnimationSet)
      {
          return;
      }
  
  #if !defined(_RELEASE)
      if (CMovieSystem::m_mov_DebugEvents)
      {
          gEnv->pScriptSystem->SetGlobalValue("CurrentCinematicName", m_name.c_str());
      }
  #endif
  
      SAnimContext animContext = ec;
      animContext.sequence = this;
      m_time = animContext.time;
  
      // Evaluate all animation nodes in sequence.
      // The director first.
      if (m_activeDirector)
      {
          m_activeDirector->Animate(animContext);
      }
  
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          // Make sure correct animation block is binded to node.
          IAnimNode* animNode = it->get();
  
          // All other (inactive) director nodes are skipped.
          if (animNode->GetType() == AnimNodeType::Director)
          {
              continue;
          }
  
          // If this is a descendant of a director node and that director is currently not active, skip this one.
          IAnimNode* parentDirector = animNode->HasDirectorAsParent();
          if (parentDirector && parentDirector != m_activeDirector)
          {
              continue;
          }
  
          if (animNode->AreFlagsSetOnNodeOrAnyParent(eAnimNodeFlags_Disabled))
          {
              continue;
          }
  
          // Animate node.
          animNode->Animate(animContext);
      }
  #if !defined(_RELEASE)
      if (CMovieSystem::m_mov_DebugEvents)
      {
          gEnv->pScriptSystem->SetGlobalToNull("CurrentCinematicName");
      }
  #endif
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Render()
  {
      for (AnimNodes::iterator it = m_nodesNeedToRender.begin(); it != m_nodesNeedToRender.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          animNode->Render();
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Activate()
  {
      if (m_bActive)
      {
          return;
      }
  
      m_bActive = true;
      // Assign animation block to all nodes in this sequence.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->OnReset();
          static_cast<CAnimNode*>(animNode)->Activate(true);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Deactivate()
  {
      if (!m_bActive)
      {
          return;
      }
  
      // Detach animation block from all nodes in this sequence.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->Activate(false);
          static_cast<CAnimNode*>(animNode)->OnReset();
      }
  
      // Remove a possibly cached game hint associated with this anim sequence.
      stack_string sTemp("anim_sequence_");
      sTemp += m_name.c_str();
      // Audio: Release precached sound
  
      m_bActive = false;
      m_precached = false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::PrecacheData(float startTime)
  {
      PrecacheStatic(startTime);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::PrecacheStatic(const float startTime)
  {
      // pre-cache animation keys
      for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->PrecacheStatic(startTime);
      }
  
      m_precachedEntitiesSet.clear();
  
      PrecacheDynamic(startTime);
  
      if (m_precached)
      {
          return;
      }
  
      // Try to cache this sequence's game hint if one exists.
      stack_string sTemp("anim_sequence_");
      sTemp += m_name.c_str();
  
      //if (gEnv->pAudioSystem)
      {
          // Make sure to use the non-serializable game hint type as trackview sequences get properly reactivated after load
          // Audio: Precache sound
      }
  
      gEnv->pLog->Log("=== Precaching render data for cutscene: %s ===", GetName());
  
      m_precached = true;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::PrecacheDynamic(float time)
  {
      // pre-cache animation keys
      for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          static_cast<CAnimNode*>(animNode)->PrecacheDynamic(time);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::PrecacheEntity(IEntity* pEntity)
  {
      if (m_precachedEntitiesSet.find(pEntity) != m_precachedEntitiesSet.end())
      {
          if (IComponentRenderPtr pRenderComponent = pEntity->GetComponent<IComponentRender>())
          {
              if (IRenderNode* pRenderNode = pRenderComponent->GetRenderNode())
              {
                  gEnv->p3DEngine->PrecacheRenderNode(pRenderNode, 4.f);
              }
          }
  
          m_precachedEntitiesSet.insert(pEntity);
      }
  }
  
  void CAnimSequence::SetId(uint32 newId)
  {
      // Notify movie system of new Id
      if (GetMovieSystem())
      {
          GetMovieSystem()->OnSetSequenceId(newId);
      }
      m_id = newId;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::Reflect(AZ::SerializeContext* serializeContext)
  {
      serializeContext->Class<CAnimSequence>()
          ->Version(4)
          ->Field("Name", &CAnimSequence::m_name)
          ->Field("SequenceEntityId", &CAnimSequence::m_sequenceEntityId)
          ->Field("Flags", &CAnimSequence::m_flags)
          ->Field("TimeRange", &CAnimSequence::m_timeRange)
          ->Field("ID", &CAnimSequence::m_id)
          ->Field("Nodes", &CAnimSequence::m_nodes)
          ->Field("SequenceType", &CAnimSequence::m_sequenceType)
          ->Field("Events", &CAnimSequence::m_events)
          ->Field("Expanded", &CAnimSequence::m_expanded)
          ->Field("ActiveDirectorNodeId", &CAnimSequence::m_activeDirectorNodeId);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::InitPostLoad()
  {
      if (m_pMovieSystem)
      {
          // Notify the MovieSystem of the new sequence Id (updates the next available Id if needed)
          m_pMovieSystem->OnSetSequenceId(GetId());
  
          // check of sequence ID collision and resolve if needed
          if (m_pMovieSystem->FindSequenceById(GetId()))
          {
              // A collision found - resolve by resetting Id. TODO: resolve all references to previous Id
              ResetId();
          }
      }
  
      IAnimNode* firstDirectorFound = nullptr;
  
      int nodeCount = GetNodeCount();
      for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
      {
          IAnimNode* animNode = GetNode(nodeIndex);
          if (animNode)
          {
              AddNode(animNode);
              animNode->InitPostLoad(this);
  
              // store the first director found
              if (!firstDirectorFound && animNode->GetType() == AnimNodeType::Director)
              {
                  firstDirectorFound = animNode;
              }
  
              // m_activeDirectorNodeId is serialized in the sequences, so set
              // this node as the active director if id's match.
              if (animNode->GetId() == m_activeDirectorNodeId)
              {
                  SetActiveDirector(animNode);
              }
          }
      }
  
      // All nodes and track have been added and m_nextTrackId is set higher than any
      // existing track id. Go over the Tracks and make sure all of track id's are assigned.
      // Track Id's are serialized and should never be zero, unless this is track data from
      // before Track Ids were added.
      for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
      {
          IAnimNode* animNode = GetNode(nodeIndex);
          if (animNode)
          {
              int trackCount = animNode->GetTrackCount();
              for (int trackIndex = 0; trackIndex < trackCount; trackIndex++)
              {
                  IAnimTrack* track = animNode->GetTrackByIndex(trackIndex);
                  AZ_Assert(track, "Expected valid track.");
                  if (track->GetId() == 0)
                  {
                      track->SetId(GetUniqueTrackIdAndGenerateNext());
                  }
  
                  int subTrackCount = track->GetSubTrackCount();
                  for (int subTrackIndex = 0; subTrackIndex < subTrackCount; subTrackIndex++)
                  {
                      IAnimTrack* subTrack = track->GetSubTrack(subTrackIndex);
                      AZ_Assert(subTrack, "Expected valid sub track.");
                      if (subTrack->GetId() == 0)
                      {
                          subTrack->SetId(GetUniqueTrackIdAndGenerateNext());
                      }
                  }
              }
          }
      }
  
      // If the active director was not set, but there was a director found,
      // set it as the active director now. This can happen if the sequence
      // was serialized before the ActiveDirectorNodeId was added.
      if (!m_activeDirector && firstDirectorFound)
      {
          SetActiveDirector(firstDirectorFound);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetTimeRange(Range timeRange)
  {
      m_timeRange = timeRange;
      // Set this time range for every track in animation.
      // Set time range to be in range of largest animation track.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* anode = it->get();
          anode->SetTimeRange(timeRange);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::AdjustKeysToTimeRange(const Range& timeRange)
  {
      float offset = timeRange.start - m_timeRange.start;
      // Calculate scale ratio.
      float scale = timeRange.Length() / m_timeRange.Length();
      m_timeRange = timeRange;
  
      // Set time range to be in range of largest animation track.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
  
          int trackCount = animNode->GetTrackCount();
          for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
          {
              IAnimTrack* pTrack = animNode->GetTrackByIndex(paramIndex);
              int nkey = pTrack->GetNumKeys();
              for (int k = 0; k < nkey; k++)
              {
                  float keytime = pTrack->GetKeyTime(k);
                  keytime = offset + keytime * scale;
                  pTrack->SetKeyTime(k, keytime);
              }
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::ComputeTimeRange()
  {
      Range timeRange = m_timeRange;
  
      // Set time range to be in range of largest animation track.
      for (AnimNodes::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
  
          int trackCount = animNode->GetTrackCount();
          for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
          {
              IAnimTrack* pTrack = animNode->GetTrackByIndex(paramIndex);
              int nkey = pTrack->GetNumKeys();
              if (nkey > 0)
              {
                  timeRange.start = std::min(timeRange.start, pTrack->GetKeyTime(0));
                  timeRange.end = std::max(timeRange.end, pTrack->GetKeyTime(nkey - 1));
              }
          }
      }
  
      if (timeRange.start > 0)
      {
          timeRange.start = 0;
      }
  
      m_timeRange = timeRange;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::AddTrackEvent(const char* szEvent)
  {
      CRY_ASSERT(szEvent && szEvent[0]);
      if (stl::push_back_unique(m_events, szEvent))
      {
          NotifyTrackEvent(ITrackEventListener::eTrackEventReason_Added, szEvent);
          return true;
      }
  
      return false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::RemoveTrackEvent(const char* szEvent)
  {
      CRY_ASSERT(szEvent && szEvent[0]);
      if (stl::find_and_erase(m_events, szEvent))
      {
          NotifyTrackEvent(ITrackEventListener::eTrackEventReason_Removed, szEvent);
          return true;
      }
  
      return false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::RenameTrackEvent(const char* szEvent, const char* szNewEvent)
  {
      CRY_ASSERT(szEvent && szEvent[0]);
      CRY_ASSERT(szNewEvent && szNewEvent[0]);
  
      for (size_t i = 0; i < m_events.size(); ++i)
      {
          if (m_events[i] == szEvent)
          {
              m_events[i] = szNewEvent;
              NotifyTrackEvent(ITrackEventListener::eTrackEventReason_Renamed, szEvent, szNewEvent);
              return true;
          }
      }
  
      return false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::MoveUpTrackEvent(const char* szEvent)
  {
      CRY_ASSERT(szEvent && szEvent[0]);
  
      for (size_t i = 0; i < m_events.size(); ++i)
      {
          if (m_events[i] == szEvent)
          {
              assert(i > 0);
              if (i > 0)
              {
                  std::swap(m_events[i - 1], m_events[i]);
                  NotifyTrackEvent(ITrackEventListener::eTrackEventReason_MovedUp, szEvent);
              }
              return true;
          }
      }
  
      return false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::MoveDownTrackEvent(const char* szEvent)
  {
      CRY_ASSERT(szEvent && szEvent[0]);
  
      for (size_t i = 0; i < m_events.size(); ++i)
      {
          if (m_events[i] == szEvent)
          {
              assert(i < m_events.size() - 1);
              if (i < m_events.size() - 1)
              {
                  std::swap(m_events[i], m_events[i + 1]);
                  NotifyTrackEvent(ITrackEventListener::eTrackEventReason_MovedDown, szEvent);
              }
              return true;
          }
      }
  
      return false;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::ClearTrackEvents()
  {
      m_events.clear();
  }
  
  //////////////////////////////////////////////////////////////////////////
  int CAnimSequence::GetTrackEventsCount() const
  {
      return (int)m_events.size();
  }
  
  //////////////////////////////////////////////////////////////////////////
  char const* CAnimSequence::GetTrackEvent(int iIndex) const
  {
      char const* szResult = NULL;
      const bool bValid = (iIndex >= 0 && iIndex < GetTrackEventsCount());
      CRY_ASSERT(bValid);
  
      if (bValid)
      {
          szResult = m_events[iIndex].c_str();
      }
  
      return szResult;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::NotifyTrackEvent(ITrackEventListener::ETrackEventReason reason,
      const char* event, const char* param)
  {
      // Notify listeners
      for (TTrackEventListeners::iterator j = m_listeners.begin(); j != m_listeners.end(); ++j)
      {
          (*j)->OnTrackEvent(this, reason, event, (void*)param);
      }
  
      // Notification via Event Bus
      Maestro::SequenceComponentNotificationBus::Event(GetSequenceEntityId(), &Maestro::SequenceComponentNotificationBus::Events::OnTrackEventTriggered, event, param);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::TriggerTrackEvent(const char* event, const char* param)
  {
      NotifyTrackEvent(ITrackEventListener::eTrackEventReason_Triggered, event, param);
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::AddTrackEventListener(ITrackEventListener* pListener)
  {
      if (std::find(m_listeners.begin(), m_listeners.end(), pListener) == m_listeners.end())
      {
          m_listeners.push_back(pListener);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::RemoveTrackEventListener(ITrackEventListener* pListener)
  {
      TTrackEventListeners::iterator it = std::find(m_listeners.begin(), m_listeners.end(), pListener);
      if (it != m_listeners.end())
      {
          m_listeners.erase(it);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::FindNodeById(int nNodeId)
  {
      for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          if (static_cast<CAnimNode*>(animNode)->GetId() == nNodeId)
          {
              return animNode;
          }
      }
      return 0;
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::FindNodeByName(const char* sNodeName, const IAnimNode* pParentDirector)
  {
      for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          // Case insensitive name comparison.
          if (_stricmp(static_cast<CAnimNode*>(animNode)->GetNameFast(), sNodeName) == 0)
          {
              bool bParentDirectorCheck = animNode->HasDirectorAsParent() == pParentDirector;
              if (bParentDirectorCheck)
              {
                  return animNode;
              }
          }
      }
      return 0;
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::ReorderNode(IAnimNode* pNode, IAnimNode* pPivotNode, bool bNext)
  {
      if (pNode == pPivotNode || !pNode)
      {
          return;
      }
  
      AZStd::intrusive_ptr<IAnimNode> pTempHolder(pNode); // Keep reference to node so it is not deleted by erasing from list.
      stl::find_and_erase_if(m_nodes, [pNode](const AZStd::intrusive_ptr<IAnimNode>& sp) { return sp.get() == pNode; });
  
      AnimNodes::iterator it;
      for (it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* animNode = it->get();
          if (animNode == pPivotNode)
          {
              if (bNext)
              {
                  m_nodes.insert(it + 1, AZStd::intrusive_ptr<IAnimNode>(pNode));
              }
              else
              {
                  m_nodes.insert(it, AZStd::intrusive_ptr<IAnimNode>(pNode));
              }
              break;
          }
      }
  
      if (it == m_nodes.end())
      {
          m_nodes.insert(m_nodes.begin(), AZStd::intrusive_ptr<IAnimNode>(pNode));
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::CopyNodeChildren(XmlNodeRef& xmlNode, IAnimNode* animNode)
  {
      for (int k = 0; k < GetNodeCount(); ++k)
      {
          if (GetNode(k)->GetParent() == animNode)
          {
              XmlNodeRef childNode = xmlNode->newChild("Node");
              GetNode(k)->Serialize(childNode, false, true);
              if (GetNode(k)->GetType() == AnimNodeType::Group
                  || animNode->GetType() == AnimNodeType::Director)
              {
                  CopyNodeChildren(xmlNode, GetNode(k));
              }
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::CopyNodes(XmlNodeRef& xmlNode, IAnimNode** pSelectedNodes, uint32 count)
  {
      for (uint32 i = 0; i < count; ++i)
      {
          IAnimNode* animNode = pSelectedNodes[i];
          if (animNode)
          {
              XmlNodeRef xn = xmlNode->newChild("Node");
              animNode->Serialize(xn, false, true);
              // If it is a group node, copy its children also.
              if (animNode->GetType() == AnimNodeType::Group || animNode->GetType() == AnimNodeType::Director)
              {
                  CopyNodeChildren(xmlNode, animNode);
              }
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::PasteNodes(const XmlNodeRef& xmlNode, IAnimNode* pParent)
  {
      int type, id;
      std::map<int, IAnimNode*> idToNode;
      for (int i = 0; i < xmlNode->getChildCount(); i++)
      {
          XmlNodeRef xn = xmlNode->getChild(i);
  
          if (!xn->getAttr("Type", type))
          {
              continue;
          }
  
          xn->getAttr("Id", id);
  
          IAnimNode* node = CreateNode((AnimNodeType)type);
          if (!node)
          {
              continue;
          }
  
          idToNode[id] = node;
  
          xn->setAttr("Id", static_cast<CAnimNode*>(node)->GetId());
          node->Serialize(xn, true, true);
  
          int parentId = 0;
          if (xn->getAttr("ParentNode", parentId))
          {
              node->SetParent(idToNode[parentId]);
          }
          else
          // This means a top-level node.
          {
              if (pParent)
              {
                  node->SetParent(pParent);
              }
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::AddNodeNeedToRender(IAnimNode* pNode)
  {
      assert(pNode != 0);
      return stl::push_back_unique(m_nodesNeedToRender, AZStd::intrusive_ptr<IAnimNode>(pNode));
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::RemoveNodeNeedToRender(IAnimNode* pNode)
  {
      assert(pNode != 0);
      stl::find_and_erase_if(m_nodesNeedToRender, [pNode](const AZStd::intrusive_ptr<IAnimNode>& sp) { return sp.get() == pNode; });
  }
  
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetSequenceEntityId(const AZ::EntityId& sequenceEntityId)
  {
      m_sequenceEntityId = sequenceEntityId;
  }
  //////////////////////////////////////////////////////////////////////////
  void CAnimSequence::SetActiveDirector(IAnimNode* pDirectorNode)
  {
      if (!pDirectorNode)
      {
          return;
      }
  
      assert(pDirectorNode->GetType() == AnimNodeType::Director);
      if (pDirectorNode->GetType() != AnimNodeType::Director)
      {
          return;     // It's not a director node.
      }
  
      if (pDirectorNode->GetSequence() != this)
      {
          return;     // It's not a node belong to this sequence.
      }
  
      m_activeDirector = pDirectorNode;
      m_activeDirectorNodeId = pDirectorNode->GetId();
  }
  
  //////////////////////////////////////////////////////////////////////////
  IAnimNode* CAnimSequence::GetActiveDirector() const
  {
      return m_activeDirector;
  }
  
  //////////////////////////////////////////////////////////////////////////
  bool CAnimSequence::IsAncestorOf(const IAnimSequence* sequence) const
  {
      assert(this != sequence);
      if (this == sequence)
      {
          return true;
      }
  
      if (!GetMovieSystem())
      {
          return false;   // should never happen, null pointer guard
      }
  
      for (AnimNodes::const_iterator it = m_nodes.begin(); it != m_nodes.end(); ++it)
      {
          IAnimNode* pNode = it->get();
          if (pNode->GetType() == AnimNodeType::Director)
          {
              IAnimTrack* pSequenceTrack = pNode->GetTrackForParameter(AnimParamType::Sequence);
              if (pSequenceTrack)
              {
                  PREFAST_ASSUME(sequence);
                  for (int i = 0; i < pSequenceTrack->GetNumKeys(); ++i)
                  {
                      ISequenceKey key;
                      pSequenceTrack->GetKey(i, &key);
                      if (_stricmp(key.szSelection.c_str(), sequence->GetName()) == 0)
                      {
                          return true;
                      }
  
                      IAnimSequence* pChild = CAnimSceneNode::GetSequenceFromSequenceKey(key);
                      if (pChild && pChild->IsAncestorOf(sequence))
                      {
                          return true;
                      }
                  }
              }
          }
      }
  
      return false;
  }
  
  
  