#region License
/* FNA - XNA4 Reimplementation for Desktop Platforms
* Copyright 2009-2016 Ethan Lee and the MonoGame Team
*
* Released under the Microsoft Public License.
* See LICENSE for details.
*/
/* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
#endregion
namespace Microsoft.Xna.Framework
{
///
/// Contains a collection of points in 2D space and provides methods for evaluating features of the curve they define.
///
[Serializable]
public class Curve
{
#region Public Properties
///
/// Returns true if this curve is constant (has zero or one points); false otherwise.
///
public bool IsConstant
{
get
{
return Keys.Count <= 1;
}
}
///
/// The collection of curve keys.
///
public CurveKeyCollection Keys
{
get;
private set;
}
///
/// Defines how to handle weighting values that are greater than the last control point in the curve.
///
public CurveLoopType PostLoop
{
get;
set;
}
///
/// Defines how to handle weighting values that are less than the first control point in the curve.
///
public CurveLoopType PreLoop
{
get;
set;
}
#endregion
#region Public Constructors
///
/// Constructs a curve.
///
public Curve()
{
Keys = new CurveKeyCollection();
}
#endregion
#region Private Constructors
private Curve(CurveKeyCollection keys)
{
Keys = keys;
}
#endregion
#region Public Methods
///
/// Creates a copy of this curve.
///
/// A copy of this curve.
public Curve Clone()
{
Curve curve = new Curve(Keys.Clone());
curve.PreLoop = PreLoop;
curve.PostLoop = PostLoop;
return curve;
}
///
/// Evaluate the value at a position of this .
///
/// The position on this .
/// Value at the position on this .
public float Evaluate(float position)
{
if (Keys.Count == 0)
{
return 0.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:
return first.Value;
case CurveLoopType.Linear:
// Linear y = a*x +b with a tangent 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)
{
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:
return last.Value;
case CurveLoopType.Linear:
// Linear y = a*x +b with a tangent 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)
{
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);
}
///
/// Computes tangents for all keys in the collection.
///
/// The tangent type for both in and out.
public void ComputeTangents(CurveTangent tangentType)
{
ComputeTangents(tangentType, tangentType);
}
///
/// Computes tangents for all keys in the collection.
///
/// The tangent in-type. for more details.
/// The tangent out-type. for more details.
public void ComputeTangents(CurveTangent tangentInType, CurveTangent tangentOutType)
{
for (int i = 0; i < Keys.Count; i += 1)
{
ComputeTangent(i, tangentInType, tangentOutType);
}
}
///
/// Computes tangent for the specific key in the collection.
///
/// The index of a key in the collection.
/// The tangent type for both in and out.
public void ComputeTangent(int keyIndex, CurveTangent tangentType)
{
ComputeTangent(keyIndex, tangentType, tangentType);
}
///
/// Computes tangent for the specific key in the collection.
///
/// The index of key in the collection.
/// The tangent in-type. for more details.
/// The tangent out-type. for more details.
public void ComputeTangent(
int keyIndex,
CurveTangent tangentInType,
CurveTangent tangentOutType
) {
// See http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.curvetangent.aspx
CurveKey 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:
float pn = p1 - p0;
if (MathHelper.WithinEpsilon(pn, 0.0f))
{
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:
float 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 -= 1;
}
return (int) cycle;
}
private float GetCurvePosition(float position)
{
// Only for position in curve.
CurveKey prev = Keys[0];
CurveKey next;
for (int i = 1; i < Keys.Count; i += 1)
{
next = Keys[i];
if (next.Position >= position)
{
if (prev.Continuity == CurveContinuity.Step)
{
if (position >= 1f)
{
return next.Value;
}
return prev.Value;
}
// To have t in [0,1]
float t = (
(position - prev.Position) /
(next.Position - prev.Position)
);
float ts = t * t;
float tss = ts * t;
/* After a lot of search on internet I have found all about
* spline function and bezier (phi'sss ancien) but finally
* used hermite curve:
* http://en.wikipedia.org/wiki/Cubic_Hermite_spline
* P(t) = (2*t^3 - 3t^2 + 1)*P0 + (t^3 - 2t^2 + t)m0 +
* (-2t^3 + 3t^2)P1 + (t^3-t^2)m1
* with P0.value = prev.value , m0 = prev.tangentOut,
* P1= next.value, m1 = next.TangentIn.
*/
return (
(2 * tss - 3 * ts + 1f) * prev.Value +
(tss - 2 * ts + t) * prev.TangentOut +
(3 * ts - 2 * tss) * next.Value +
(tss - ts) * next.TangentIn
);
}
prev = next;
}
return 0f;
}
#endregion
}
}