context: // MIT License - Copyright (C) The Mono.Xna Team
  // This file is subject to the terms and conditions defined in
  // file 'LICENSE.txt', which is part of this source code package.
  
  using System;
  using System.ComponentModel;
  using System.Runtime.Serialization;
  
  namespace Microsoft.Xna.Framework
  {
      /// <summary>
      /// Contains a collection of <see cref="CurveKey"/> points in 2D space and provides methods for evaluating features of the curve they define.
      /// </summary>
      // TODO : [TypeConverter(typeof(ExpandableObjectConverter))]
      [DataContract]
      public class Curve
      {
          #region Private Fields
  
          private CurveKeyCollection _keys;
          private CurveLoopType _postLoop;
          private CurveLoopType _preLoop;
  
          #endregion
  
          #region Public Properties
  
          /// <summary>
          /// Returns <c>true</c> if this curve is constant (has zero or one points); <c>false</c> otherwise.
          /// </summary>
          [DataMember]
          public bool IsConstant
          {
              get { return this._keys.Count <= 1; }
          }
  
          /// <summary>
          /// The collection of curve keys.
          /// </summary>
          [DataMember]
          public CurveKeyCollection Keys
          {
              get { return this._keys; }
          }
  
          /// <summary>
          /// Defines how to handle weighting values that are greater than the last control point in the curve.
          /// </summary>
          [DataMember]
          public CurveLoopType PostLoop
          {
              get { return this._postLoop; }
              set { this._postLoop = value; }
          }
  
          /// <summary>
          /// Defines how to handle weighting values that are less than the first control point in the curve.
          /// </summary>
          [DataMember]
          public CurveLoopType PreLoop
          {
              get { return this._preLoop; }
              set { this._preLoop = value; }
          }
  
          #endregion
  
          #region Public Constructors
  
          /// <summary>
          /// Constructs a curve.
          /// </summary>
          public Curve()
          {
              this._keys = new CurveKeyCollection();
          }
  
          #endregion
  
          #region Public Methods
  
          /// <summary>
          /// Creates a copy of this curve.
          /// </summary>
          /// <returns>A copy of this curve.</returns>
          public Curve Clone()
          {
              Curve curve = new Curve();
  
              curve._keys = this._keys.Clone();
              curve._preLoop = this._preLoop;
              curve._postLoop = this._postLoop;
  
              return curve;
          }
  
          /// <summary>
          /// Evaluate the value at a position of this <see cref="Curve"/>.
          /// </summary>
          /// <param name="position">The position on this <see cref="Curve"/>.</param>
          /// <returns>Value at the position on this <see cref="Curve"/>.</returns>
          public float Evaluate(float position)
          {
              if (_keys.Count == 0)
              {
              	return 0f;
              }
  						
              if (_keys.Count == 1)
              {
              	return _keys[0].Value;
              }
  			
              CurveKey first = _keys[0];
              CurveKey last = _keys[_keys.Count - 1];
  
              if (position < first.Position)
              {
                  switch (this.PreLoop)
                  {
                      case CurveLoopType.Constant:
                          //constant
                          return first.Value;
  
                      case CurveLoopType.Linear:
                          // linear y = a*x +b with a tangeant of last point
                          return first.Value - first.TangentIn * (first.Position - position);
  
                      case CurveLoopType.Cycle:
                          //start -> end / start -> end
                          int cycle = GetNumberOfCycle(position);
                          float virtualPos = position - (cycle * (last.Position - first.Position));
                          return GetCurvePosition(virtualPos);
  
                      case CurveLoopType.CycleOffset:
                          //make the curve continue (with no step) so must up the curve each cycle of delta(value)
                          cycle = GetNumberOfCycle(position);
                          virtualPos = position - (cycle * (last.Position - first.Position));
                          return (GetCurvePosition(virtualPos) + cycle * (last.Value - first.Value));
  
                      case CurveLoopType.Oscillate:
                          //go back on curve from end and target start 
                          // start-> end / end -> start
                          cycle = GetNumberOfCycle(position);
                          if (0 == cycle % 2f)//if pair
                              virtualPos = position - (cycle * (last.Position - first.Position));
                          else
                              virtualPos = last.Position - position + first.Position + (cycle * (last.Position - first.Position));
                          return GetCurvePosition(virtualPos);
                  }
              }
              else if (position > last.Position)
              {
                  int cycle;
                  switch (this.PostLoop)
                  {
                      case CurveLoopType.Constant:
                          //constant
                          return last.Value;
  
                      case CurveLoopType.Linear:
                          // linear y = a*x +b with a tangeant of last point
                          return last.Value + first.TangentOut * (position - last.Position);
  
                      case CurveLoopType.Cycle:
                          //start -> end / start -> end
                          cycle = GetNumberOfCycle(position);
                          float virtualPos = position - (cycle * (last.Position - first.Position));
                          return GetCurvePosition(virtualPos);
  
                      case CurveLoopType.CycleOffset:
                          //make the curve continue (with no step) so must up the curve each cycle of delta(value)
                          cycle = GetNumberOfCycle(position);
                          virtualPos = position - (cycle * (last.Position - first.Position));
                          return (GetCurvePosition(virtualPos) + cycle * (last.Value - first.Value));
  
                      case CurveLoopType.Oscillate:
                          //go back on curve from end and target start 
                          // start-> end / end -> start
                          cycle = GetNumberOfCycle(position);
                          virtualPos = position - (cycle * (last.Position - first.Position));
                          if (0 == cycle % 2f)//if pair
                              virtualPos = position - (cycle * (last.Position - first.Position));
                          else
                              virtualPos = last.Position - position + first.Position + (cycle * (last.Position - first.Position));
                          return GetCurvePosition(virtualPos);
                  }
              }
  
              //in curve
              return GetCurvePosition(position);
          }
  
          /// <summary>
          /// Computes tangents for all keys in the collection.
          /// </summary>
          /// <param name="tangentType">The tangent type for both in and out.</param>
  		public void ComputeTangents (CurveTangent tangentType)
  		{
  		    ComputeTangents(tangentType, tangentType);
  		}
  		
          /// <summary>
          /// Computes tangents for all keys in the collection.
          /// </summary>
          /// <param name="tangentInType">The tangent in-type. <see cref="CurveKey.TangentIn"/> for more details.</param>
          /// <param name="tangentOutType">The tangent out-type. <see cref="CurveKey.TangentOut"/> for more details.</param>
  		public void ComputeTangents(CurveTangent tangentInType, CurveTangent tangentOutType)
  		{
              for (var i = 0; i < Keys.Count; ++i)
              {
                  ComputeTangent(i, tangentInType, tangentOutType);
              }
  		}
  
          /// <summary>
          /// Computes tangent for the specific key in the collection.
          /// </summary>
          /// <param name="keyIndex">The index of a key in the collection.</param>
          /// <param name="tangentType">The tangent type for both in and out.</param>
          public void ComputeTangent(int keyIndex, CurveTangent tangentType)
          {
              ComputeTangent(keyIndex, tangentType, tangentType);
          }
  
          /// <summary>
          /// Computes tangent for the specific key in the collection.
          /// </summary>
          /// <param name="keyIndex">The index of key in the collection.</param>
          /// <param name="tangentInType">The tangent in-type. <see cref="CurveKey.TangentIn"/> for more details.</param>
          /// <param name="tangentOutType">The tangent out-type. <see cref="CurveKey.TangentOut"/> for more details.</param>
          public void ComputeTangent(int keyIndex, CurveTangent tangentInType, CurveTangent tangentOutType)
          {
              // See http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.curvetangent.aspx
  
              var key = _keys[keyIndex];
  
              float p0, p, p1;
              p0 = p = p1 = key.Position;
  
              float v0, v, v1;
              v0 = v = v1 = key.Value;
  
              if ( keyIndex > 0 )
              {
                  p0 = _keys[keyIndex - 1].Position;
                  v0 = _keys[keyIndex - 1].Value;
              }
  
              if (keyIndex < _keys.Count-1)
              {
                  p1 = _keys[keyIndex + 1].Position;
                  v1 = _keys[keyIndex + 1].Value;
              }
  
              switch (tangentInType)
              {
                  case CurveTangent.Flat:
                      key.TangentIn = 0;
                      break;
                  case CurveTangent.Linear:
                      key.TangentIn = v - v0;
                      break;
                  case CurveTangent.Smooth:
                      var pn = p1 - p0;
                      if (Math.Abs(pn) < float.Epsilon)
                          key.TangentIn = 0;
                      else
                          key.TangentIn = (v1 - v0) * ((p - p0) / pn);
                      break;
              }
  
              switch (tangentOutType)
              {
                  case CurveTangent.Flat:
                      key.TangentOut = 0;
                      break;
                  case CurveTangent.Linear:
                      key.TangentOut = v1 - v;
                      break;
                  case CurveTangent.Smooth:
                      var pn = p1 - p0;
                      if (Math.Abs(pn) < float.Epsilon)
                          key.TangentOut = 0;
                      else
                          key.TangentOut = (v1 - v0) * ((p1 - p) / pn);
                      break;
              }
          }
  
  	    #endregion
  
          #region Private Methods
  
          private int GetNumberOfCycle(float position)
          {
              float cycle = (position - _keys[0].Position) / (_keys[_keys.Count - 1].Position - _keys[0].Position);
              if (cycle < 0f)
                  cycle--;
              return (int)cycle;
          }
  
          private float GetCurvePosition(float position)
          {
              //only for position in curve
  
gt:             CurveKey prev = this._keys[0];