text: /*
   * 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
   *
   */
  
  
  #pragma once
  
  #include <LyShine/Animation/IUiAnimation.h>
  //#include "TCBSpline.h"
  #include "2DSpline.h"
  
  #define MIN_TIME_PRECISION 0.01f
  #define MIN_VALUE_RANGE 1.0f        // prevents fill sliders from being inoperable on the first key frame
  /*!
          Templated class that used as a base for all Tcb spline tracks.
   */
  template <class ValueType>
  class TUiAnimSplineTrack
      : public IUiAnimTrack
  {
  public:
      AZ_CLASS_ALLOCATOR(TUiAnimSplineTrack, AZ::SystemAllocator, 0)
      AZ_RTTI((TUiAnimSplineTrack, "{A78AAC62-84D0-4E2E-958E-564F51A140D2}", Vec2), IUiAnimTrack);
  
      TUiAnimSplineTrack()
          : m_refCount(0)
      {
          AllocSpline();
          m_flags = 0;
          m_bCustomColorSet = false;
          m_fMinKeyValue = 0.0f;
          m_fMaxKeyValue = 0.0f;
      }
      ~TUiAnimSplineTrack()
      {
          m_spline.reset();
      }
  
      //////////////////////////////////////////////////////////////////////////
      void add_ref() override;
      void release() override;
      //////////////////////////////////////////////////////////////////////////
  
      virtual int GetSubTrackCount() const { return 0; };
      virtual IUiAnimTrack* GetSubTrack([[maybe_unused]] int nIndex) const { return 0; };
      virtual const char* GetSubTrackName([[maybe_unused]] int nIndex) const { return NULL; };
      virtual void SetSubTrackName([[maybe_unused]] int nIndex, [[maybe_unused]] const char* name) { assert(0); }
  
      virtual const CUiAnimParamType& GetParameterType() const { return m_nParamType; };
      virtual void SetParameterType(CUiAnimParamType type) { m_nParamType = type; };
  
      virtual const UiAnimParamData& GetParamData() const { return m_componentParamData; }
      virtual void SetParamData(const UiAnimParamData& param) { m_componentParamData = param; }
  
      virtual void GetKeyValueRange(float& fMin, float& fMax) const { fMin = m_fMinKeyValue; fMax = m_fMaxKeyValue; };
      virtual void SetKeyValueRange(float fMin, float fMax){ m_fMinKeyValue = fMin; m_fMaxKeyValue = fMax; };
  
      ISplineInterpolator* GetSpline() const { return m_spline.get(); };
  
      virtual bool IsKeySelected(int key) const
      {
          if (GetSpline() && GetSpline()->IsKeySelectedAtAnyDimension(key))
          {
              return true;
          }
          return false;
      }
  
      virtual void SelectKey(int key, bool select)
      {
          if (GetSpline())
          {
              GetSpline()->SelectKeyAllDimensions(key, select);
          }
      }
  
      int GetNumKeys() const
      {
          return m_spline->num_keys();
      }
  
      void SetNumKeys(int numKeys)
      {
          m_spline->resize(numKeys);
      }
  
      bool HasKeys() const
      {
          return GetNumKeys() != 0;
      }
  
      void RemoveKey(int num)
      {
          if (m_spline && m_spline->num_keys() > num)
          {
              m_spline->erase(num);
          }
          else
          {
              assert(0);
          }
      }
  
      void GetKey(int index, IKey* key) const
      {
          assert(index >= 0 && index < GetNumKeys());
          assert(key != 0);
          typename Spline::key_type& k = m_spline->key(index);
          ITcbKey* tcbkey = (ITcbKey*)key;
          tcbkey->time = k.time;
          tcbkey->flags = k.flags;
  
          tcbkey->tens = k.tens;
          tcbkey->cont = k.cont;
          tcbkey->bias = k.bias;
          tcbkey->easeto = k.easeto;
          tcbkey->easefrom = k.easefrom;
  
          tcbkey->SetValue(k.value);
      }
  
      void SetKey(int index, IKey* key)
      {
          assert(index >= 0 && index < GetNumKeys());
          assert(key != 0);
          typename Spline::key_type& k = m_spline->key(index);
          ITcbKey* tcbkey = (ITcbKey*)key;
          k.time = tcbkey->time;
          k.flags = tcbkey->flags;
          k.tens = tcbkey->tens;
          k.cont = tcbkey->cont;
          k.bias = tcbkey->bias;
          k.easeto = tcbkey->easeto;
          k.easefrom = tcbkey->easefrom;
          tcbkey->GetValue(k.value);
          Invalidate();
      }
  
      float GetKeyTime(int index) const
      {
          assert(index >= 0 && index < GetNumKeys());
          return m_spline->time(index);
      }
      void SetKeyTime(int index, float time)
      {
          assert(index >= 0 && index < GetNumKeys());
          m_spline->SetKeyTime(index, time);
          Invalidate();
      }
      int GetKeyFlags(int index)
      {
          assert(index >= 0 && index < GetNumKeys());
          return m_spline->key(index).flags;
      }
      void SetKeyFlags(int index, int flags)
      {
          assert(index >= 0 && index < GetNumKeys());
          m_spline->key(index).flags = flags;
      }
  
      virtual EUiAnimCurveType GetCurveType() { assert(0); return eUiAnimCurveType_Unknown; }
      virtual EUiAnimValue GetValueType() { assert(0); return eUiAnimValue_Unknown; }
  
      virtual void GetValue(float time, float& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] Vec3& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] Vec4& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] Quat& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] bool& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] AZ::Vector2& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] AZ::Vector3& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] AZ::Vector4& value) { assert(0); }
      virtual void GetValue([[maybe_unused]] float time, [[maybe_unused]] AZ::Color& value) { assert(0); }
  
      virtual void SetValue(float time, const float& value, bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const Vec3& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const Vec4& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const Quat& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const bool& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const AZ::Vector2& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const AZ::Vector3& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const AZ::Vector4& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
      virtual void SetValue([[maybe_unused]] float time, [[maybe_unused]] const AZ::Color& value, [[maybe_unused]] bool bDefault = false) { assert(0); }
  
      virtual void OffsetKeyPosition([[maybe_unused]] const Vec3& value) { assert(0); };
  
      bool Serialize(IUiAnimationSystem* uiAnimationSystem, XmlNodeRef& xmlNode, bool bLoading, bool bLoadEmptyTracks);
      bool SerializeSelection(XmlNodeRef& xmlNode, bool bLoading, bool bCopySelected, float fTimeOffset);
  
      void GetKeyInfo(int key, const char*& description, float& duration)
      {
          description = 0;
          duration = 0;
      }
  
      //! Sort keys in track (after time of keys was modified).
      void SortKeys()
      {
          m_spline->sort_keys();
      };
  
      //! Get track flags.
      int GetFlags() { return m_flags; }
  
      //! Check if track is masked by mask
      virtual bool IsMasked([[maybe_unused]] const uint32 mask) const { return false; }
  
      //! Set track flags.
      void SetFlags(int flags)
      {
          m_flags = flags;
          if (m_flags & eUiAnimTrackFlags_Loop)
          {
              m_spline->ORT(Spline::ORT_LOOP);
          }
          else if (m_flags & eUiAnimTrackFlags_Cycle)
          {
              m_spline->ORT(Spline::ORT_CYCLE);
          }
          else
          {
              m_spline->ORT(Spline::ORT_CONSTANT);
          }
      }
  
      void Invalidate()
      {
          m_spline->flag_set(Spline::MODIFIED);
      };
  
      void SetTimeRange(const Range& timeRange)
      {
          m_spline->SetRange(timeRange.start, timeRange.end);
      }
  
      int FindKey(float time)
      {
          // Find key with given time.
          int num = m_spline->num_keys();
          for (int i = 0; i < num; i++)
          {
              float keyt = m_spline->key(i).time;
              if (fabs(keyt - time) < MIN_TIME_PRECISION)
              {
                  return i;
              }
          }
          return -1;
      }
  
      //! Create key at given time, and return its index.
      int CreateKey(float time)
      {
          ValueType value;
  
          int nkey = GetNumKeys();
  
          if (nkey > 0)
          {
              GetValue(time, value);
          }
          else
          {
              value = m_defaultValue;
          }
  
          typename Spline::ValueType tmp;
          m_spline->ToValueType(value, tmp);
          return m_spline->InsertKey(time, tmp);
      }
  
      int CloneKey(int srcKey)
      {
          return CopyKey(this, srcKey);
      }
  
      int CopyKey(IUiAnimTrack* pFromTrack, int nFromKey)
      {
          ITcbKey key;
          pFromTrack->GetKey(nFromKey, &key);
          int nkey = GetNumKeys();
          SetNumKeys(nkey + 1);
          SetKey(nkey, &key);
          return nkey;
      }
  
      //! Get key at given time,
      //! If key not exist adds key at this time.
      void SetKeyAtTime(float time, IKey* key)
      {
          assert(key != 0);
  
          key->time = time;
  
          bool found = false;
          // Find key with given time.
          for (int i = 0; i < m_spline->num_keys(); i++)
          {
              float keyt = m_spline->key(i).time;
              if (fabs(keyt - time) < MIN_TIME_PRECISION)
              {
                  key->flags = m_spline->key(i).flags;                // Reserve the flag value.
                  SetKey(i, key);
                  found = true;
                  break;
              }
              //if (keyt > time)
              //break;
          }
          if (!found)
          {
              // Key with this time not found.
              // Create a new one.
              int keyIndex = CreateKey(time);
              // Reserve the flag value.
              key->flags = m_spline->key(keyIndex).flags;     // Reserve the flag value.
              SetKey(keyIndex, key);
          }
      }
  
      virtual void SetDefaultValue(const ValueType& value)
      {
          m_defaultValue = value;
      }
  
      virtual ColorB GetCustomColor() const
      { return m_customColor; }
      virtual void SetCustomColor(ColorB color)
      {
          m_customColor = color;
          m_bCustomColorSet = true;
      }
      virtual bool HasCustomColor() const
      { return m_bCustomColorSet; }
      virtual void ClearCustomColor()
      { m_bCustomColorSet = false; }
  
      static void Reflect(AZ::SerializeContext* serializeContext) {}
  
  protected:
  
      void UpdateTrackValueRange(float newValue)
      {
          m_fMinKeyValue = (newValue < m_fMinKeyValue) ? newValue : m_fMinKeyValue;
          m_fMaxKeyValue = (newValue > m_fMaxKeyValue) ? newValue : m_fMaxKeyValue;
          if ((m_fMaxKeyValue - m_fMinKeyValue) < MIN_VALUE_RANGE)
          {
              // prevents fill sliders from being inoperable when min and max are identical (or close to it)
              m_fMaxKeyValue = (m_fMinKeyValue + MIN_VALUE_RANGE);
          }
      };
  
  private:
      //! Spawns new instance of Tcb spline.
      void AllocSpline()
      {
          m_spline = aznew UiSpline::TrackSplineInterpolator<ValueType>;
      }
  
      int m_refCount;
  
      typedef UiSpline::TrackSplineInterpolator<ValueType> Spline;
      AZStd::intrusive_ptr<Spline> m_spline;
      ValueType m_defaultValue;
  
      //! Keys of float track.
      int m_flags;
      CUiAnimParamType m_nParamType;
  
      ColorB m_customColor;
      bool m_bCustomColorSet;
  
      float m_fMinKeyValue;
      float m_fMaxKeyValue;
  
      UiAnimParamData m_componentParamData;
  
      static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) {};
  };
  
  //////////////////////////////////////////////////////////////////////////
  template <class T>
  inline void TUiAnimSplineTrack<T>::add_ref()
  {
      ++m_refCount;
  }
  
  //////////////////////////////////////////////////////////////////////////
  template <class T>
  inline void TUiAnimSplineTrack<T>::release()
  {
      if (--m_refCount <= 0)
      {
          delete this;
      }
  }
  
  template <class T>
  inline bool TUiAnimSplineTrack<T>::Serialize(IUiAnimationSystem* uiAnimationSystem, XmlNodeRef& xmlNode, bool bLoading, bool bLoadEmptyTracks)
  {
      if (bLoading)
      {
          int num = xmlNode->getChildCount();
  
          int flags = m_flags;
          xmlNode->getAttr("Flags", flags);
          xmlNode->getAttr("defaultValue", m_defaultValue);
          SetFlags(flags);
          xmlNode->getAttr("HasCustomColor", m_bCustomColorSet);
  
          if (m_bCustomColorSet)
          {
              unsigned int abgr;
              xmlNode->getAttr("CustomColor", abgr);
              m_customColor = ColorB(abgr);
          }
  
          T value;
          SetNumKeys(num);
          for (int i = 0; i < num; i++)
          {
              ITcbKey key; // Must be inside loop.
  
              XmlNodeRef keyNode = xmlNode->getChild(i);
              keyNode->getAttr("time", key.time);
  
              if (keyNode->getAttr("value", value))
              {
                  key.SetValue(value);
              }
  
              keyNode->getAttr("tens", key.tens);
              keyNode->getAttr("cont", key.cont);
              keyNode->getAttr("bias", key.bias);
              keyNode->getAttr("easeto", key.easeto);
              keyNode->getAttr("easefrom", key.easefrom);
              keyNode->getAttr("flags", key.flags);
  
              SetKey(i, &key);
  
              // In-/Out-tangent
              keyNode->getAttr("ds", m_spline->key(i).ds);
              keyNode->getAttr("dd", m_spline->key(i).dd);
          }
  
          if ((!num) && (!bLoadEmptyTracks))
          {
              return false;
          }
      }
      else
      {
          int num = GetNumKeys();
          xmlNode->setAttr("Flags", GetFlags());
          xmlNode->setAttr("defaultValue", m_defaultValue);
          xmlNode->setAttr("HasCustomColor", m_bCustomColorSet);
          if (m_bCustomColorSet)
          {
              xmlNode->setAttr("CustomColor", m_customColor.pack_abgr8888());
          }
  
          ITcbKey key;
          T value;
          for (int i = 0; i < num; i++)
          {
              GetKey(i, &key);
              XmlNodeRef keyNode = xmlNode->newChild("Key");
              keyNode->setAttr("time", key.time);
  
              key.GetValue(value);
              keyNode->setAttr("value", value);
  
              if (key.tens != 0)
              {
                  keyNode->setAttr("tens", key.tens);
              }
              if (key.cont != 0)
              {
                  keyNode->setAttr("cont", key.cont);
              }
              if (key.bias != 0)
              {
                  keyNode->setAttr("bias", key.bias);
              }
              if (key.easeto != 0)
              {
                  keyNode->setAttr("easeto", key.easeto);
              }
              if (key.easefrom != 0)
              {
                  keyNode->setAttr("easefrom", key.easefrom);
              }
  
              int flags = key.flags;
              // Just save the in/out mask part. Others are for editing convenience.
              flags &= (SPLINE_KEY_TANGENT_IN_MASK | SPLINE_KEY_TANGENT_OUT_MASK);
              if (flags != 0)
              {
                  keyNode->setAttr("flags", flags);
              }
  
              // We also have to save in-/out-tangents, because TCB infos are not used for custom tangent keys.
              keyNode->setAttr("ds", m_spline->key(i).ds);
              keyNode->setAttr("dd", m_spline->key(i).dd);
          }
      }
      return true;
  }
  
  template <class T>
  inline bool TUiAnimSplineTrack<T>::SerializeSelection(XmlNodeRef& xmlNode, bool bLoading, bool bCopySelected, float fTimeOffset)
  {
      if (bLoading)
      {
          int numCur = GetNumKeys();
          int num = xmlNode->getChildCount();
  
          int type;
          xmlNode->getAttr("TrackType", type);
  
          if (type != GetCurveType())
          {
              return false;
          }
  
          T value;
          SetNumKeys(num + numCur);
          for (int i = 0; i < num; i++)
          {
              ITcbKey key; // Must be inside loop.
  
              XmlNodeRef keyNode = xmlNode->getChild(i);
              keyNode->getAttr("time", key.time);
              key.time += fTimeOffset;
  
              if (keyNode->getAttr("value", value))
              {
                  key.SetValue(value);
              }
  
              keyNode->getAttr("tens", key.tens);
              keyNode->getAttr("cont", key.cont);
              keyNode->getAttr("bias", key.bias);
              keyNode->getAttr("easeto", key.easeto);
              keyNode->getAttr("easefrom", key.easefrom);
              keyNode->getAttr("flags", key.flags);
  
              SetKey(i + numCur, &key);
  
              if (bCopySelected)
              {
                  SelectKey(i + numCur, true);
              }
  
              // In-/Out-tangent
              keyNode->getAttr("ds", m_spline->key(i + numCur).ds);
              keyNode->getAttr("dd", m_spline->key(i + numCur).dd);
          }
          SortKeys();
      }
      else
      {
          int num = GetNumKeys();
          xmlNode->setAttr("TrackType", GetCurveType());
  
          ITcbKey key;
          T value;
          for (int i = 0; i < num; i++)
          {
              GetKey(i, &key);
  
              if (!bCopySelected || IsKeySelected(i))
              {
                  XmlNodeRef keyNode = xmlNode->newChild("Key");
                  keyNode->setAttr("time", key.time);
  
                  key.GetValue(value);
                  keyNode->setAttr("value", value);
  
                  if (key.tens != 0)
                  {
                      keyNode->setAttr("tens", key.tens);
                  }
                  if (key.cont != 0)
                  {
                      keyNode->setAttr("cont", key.cont);
                  }
                  if (key.bias != 0)
                  {
                      keyNode->setAttr("bias", key.bias);
                  }
                  if (key.easeto != 0)
                  {
                      keyNode->setAttr("easeto", key.easeto);
                  }
                  if (key.easefrom != 0)
                  {
                      keyNode->setAttr("easefrom", key.easefrom);
                  }
  
                  int flags = key.flags;
                  // Just save the in/out mask part. Others are for editing convenience.
                  flags &= (SPLINE_KEY_TANGENT_IN_MASK | SPLINE_KEY_TANGENT_OUT_MASK);
                  if (flags != 0)
                  {
                      keyNode->setAttr("flags", flags);
                  }
  
                  // We also have to save in-/out-tangents, because TCB infos are not used for custom tangent keys.
                  keyNode->setAttr("ds", m_spline->key(i).ds);
                  keyNode->setAttr("dd", m_spline->key(i).dd);
              }
          }
      }
      return true;
  }
  
  // This is the current main.
  #include "AnimSplineTrack_Vec2Specialization.h"
  typedef TUiAnimSplineTrack<Vec2>        C2DSplineTrack;
  
  #if 0
  // Followings are deprecated and supported only for the backward compatibility.
  #include "AnimSplineTrack_FloatSpecialization.h"
  #include "AnimSplineTrack_Vec3Specialization.h"
  #include "AnimSplineTrack_QuatSpecialization.h"
  typedef TUiAnimSplineTrack<float>   CTcbFloatTrack;
  typedef TUiAnimSplineTrack<Vec3>        CTcbVectorTrack;
  typedef TUiAnimSplineTrack<Quat>        CTcbQuatTrack;
  #endif
  