341 lines
11 KiB
C#
341 lines
11 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Text;
|
|||
|
using System.Xml.Serialization;
|
|||
|
using Microsoft.Xna.Framework;
|
|||
|
|
|||
|
namespace FarseerPhysics.Common
|
|||
|
{
|
|||
|
//Contributed by Matthew Bettcher
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Path:
|
|||
|
/// Very similar to Vertices, but this
|
|||
|
/// class contains vectors describing
|
|||
|
/// control points on a Catmull-Rom
|
|||
|
/// curve.
|
|||
|
/// </summary>
|
|||
|
[XmlRoot("Path")]
|
|||
|
public class Path
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// All the points that makes up the curve
|
|||
|
/// </summary>
|
|||
|
[XmlElement("ControlPoints")]
|
|||
|
public List<Vector2> ControlPoints;
|
|||
|
|
|||
|
private float _deltaT;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes a new instance of the <see cref="Path"/> class.
|
|||
|
/// </summary>
|
|||
|
public Path()
|
|||
|
{
|
|||
|
ControlPoints = new List<Vector2>();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes a new instance of the <see cref="Path"/> class.
|
|||
|
/// </summary>
|
|||
|
/// <param name="vertices">The vertices to created the path from.</param>
|
|||
|
public Path(Vector2[] vertices)
|
|||
|
{
|
|||
|
ControlPoints = new List<Vector2>(vertices.Length);
|
|||
|
|
|||
|
for (int i = 0; i < vertices.Length; i++)
|
|||
|
{
|
|||
|
Add(vertices[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes a new instance of the <see cref="Path"/> class.
|
|||
|
/// </summary>
|
|||
|
/// <param name="vertices">The vertices to created the path from.</param>
|
|||
|
public Path(IList<Vector2> vertices)
|
|||
|
{
|
|||
|
ControlPoints = new List<Vector2>(vertices.Count);
|
|||
|
for (int i = 0; i < vertices.Count; i++)
|
|||
|
{
|
|||
|
Add(vertices[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// True if the curve is closed.
|
|||
|
/// </summary>
|
|||
|
/// <value><c>true</c> if closed; otherwise, <c>false</c>.</value>
|
|||
|
[XmlElement("Closed")]
|
|||
|
public bool Closed { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the next index of a controlpoint
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">The index.</param>
|
|||
|
/// <returns></returns>
|
|||
|
public int NextIndex(int index)
|
|||
|
{
|
|||
|
if (index == ControlPoints.Count - 1)
|
|||
|
{
|
|||
|
return 0;
|
|||
|
}
|
|||
|
return index + 1;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the previous index of a controlpoint
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">The index.</param>
|
|||
|
/// <returns></returns>
|
|||
|
public int PreviousIndex(int index)
|
|||
|
{
|
|||
|
if (index == 0)
|
|||
|
{
|
|||
|
return ControlPoints.Count - 1;
|
|||
|
}
|
|||
|
return index - 1;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Translates the control points by the specified vector.
|
|||
|
/// </summary>
|
|||
|
/// <param name="vector">The vector.</param>
|
|||
|
public void Translate(ref Vector2 vector)
|
|||
|
{
|
|||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
|||
|
ControlPoints[i] = Vector2.Add(ControlPoints[i], vector);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Scales the control points by the specified vector.
|
|||
|
/// </summary>
|
|||
|
/// <param name="value">The Value.</param>
|
|||
|
public void Scale(ref Vector2 value)
|
|||
|
{
|
|||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
|||
|
ControlPoints[i] = Vector2.Multiply(ControlPoints[i], value);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Rotate the control points by the defined value in radians.
|
|||
|
/// </summary>
|
|||
|
/// <param name="value">The amount to rotate by in radians.</param>
|
|||
|
public void Rotate(float value)
|
|||
|
{
|
|||
|
Matrix rotationMatrix;
|
|||
|
Matrix.CreateRotationZ(value, out rotationMatrix);
|
|||
|
|
|||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
|||
|
ControlPoints[i] = Vector2.Transform(ControlPoints[i], rotationMatrix);
|
|||
|
}
|
|||
|
|
|||
|
public override string ToString()
|
|||
|
{
|
|||
|
StringBuilder builder = new StringBuilder();
|
|||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
|||
|
{
|
|||
|
builder.Append(ControlPoints[i].ToString());
|
|||
|
if (i < ControlPoints.Count - 1)
|
|||
|
{
|
|||
|
builder.Append(" ");
|
|||
|
}
|
|||
|
}
|
|||
|
return builder.ToString();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns a set of points defining the
|
|||
|
/// curve with the specifed number of divisions
|
|||
|
/// between each control point.
|
|||
|
/// </summary>
|
|||
|
/// <param name="divisions">Number of divisions between each control point.</param>
|
|||
|
/// <returns></returns>
|
|||
|
public Vertices GetVertices(int divisions)
|
|||
|
{
|
|||
|
Vertices verts = new Vertices();
|
|||
|
|
|||
|
float timeStep = 1f / divisions;
|
|||
|
|
|||
|
for (float i = 0; i < 1f; i += timeStep)
|
|||
|
{
|
|||
|
verts.Add(GetPosition(i));
|
|||
|
}
|
|||
|
|
|||
|
return verts;
|
|||
|
}
|
|||
|
|
|||
|
public Vector2 GetPosition(float time)
|
|||
|
{
|
|||
|
Vector2 temp;
|
|||
|
|
|||
|
if (ControlPoints.Count < 2)
|
|||
|
throw new Exception("You need at least 2 control points to calculate a position.");
|
|||
|
|
|||
|
if (Closed)
|
|||
|
{
|
|||
|
Add(ControlPoints[0]);
|
|||
|
|
|||
|
_deltaT = 1f / (ControlPoints.Count - 1);
|
|||
|
|
|||
|
int p = (int)(time / _deltaT);
|
|||
|
|
|||
|
// use a circular indexing system
|
|||
|
int p0 = p - 1;
|
|||
|
if (p0 < 0) p0 = p0 + (ControlPoints.Count - 1);
|
|||
|
else if (p0 >= ControlPoints.Count - 1) p0 = p0 - (ControlPoints.Count - 1);
|
|||
|
int p1 = p;
|
|||
|
if (p1 < 0) p1 = p1 + (ControlPoints.Count - 1);
|
|||
|
else if (p1 >= ControlPoints.Count - 1) p1 = p1 - (ControlPoints.Count - 1);
|
|||
|
int p2 = p + 1;
|
|||
|
if (p2 < 0) p2 = p2 + (ControlPoints.Count - 1);
|
|||
|
else if (p2 >= ControlPoints.Count - 1) p2 = p2 - (ControlPoints.Count - 1);
|
|||
|
int p3 = p + 2;
|
|||
|
if (p3 < 0) p3 = p3 + (ControlPoints.Count - 1);
|
|||
|
else if (p3 >= ControlPoints.Count - 1) p3 = p3 - (ControlPoints.Count - 1);
|
|||
|
|
|||
|
// relative time
|
|||
|
float lt = (time - _deltaT * p) / _deltaT;
|
|||
|
|
|||
|
temp = Vector2.CatmullRom(ControlPoints[p0], ControlPoints[p1], ControlPoints[p2], ControlPoints[p3], lt);
|
|||
|
|
|||
|
RemoveAt(ControlPoints.Count - 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
int p = (int)(time / _deltaT);
|
|||
|
|
|||
|
//
|
|||
|
int p0 = p - 1;
|
|||
|
if (p0 < 0) p0 = 0;
|
|||
|
else if (p0 >= ControlPoints.Count - 1) p0 = ControlPoints.Count - 1;
|
|||
|
int p1 = p;
|
|||
|
if (p1 < 0) p1 = 0;
|
|||
|
else if (p1 >= ControlPoints.Count - 1) p1 = ControlPoints.Count - 1;
|
|||
|
int p2 = p + 1;
|
|||
|
if (p2 < 0) p2 = 0;
|
|||
|
else if (p2 >= ControlPoints.Count - 1) p2 = ControlPoints.Count - 1;
|
|||
|
int p3 = p + 2;
|
|||
|
if (p3 < 0) p3 = 0;
|
|||
|
else if (p3 >= ControlPoints.Count - 1) p3 = ControlPoints.Count - 1;
|
|||
|
|
|||
|
// relative time
|
|||
|
float lt = (time - _deltaT * p) / _deltaT;
|
|||
|
|
|||
|
temp = Vector2.CatmullRom(ControlPoints[p0], ControlPoints[p1], ControlPoints[p2], ControlPoints[p3], lt);
|
|||
|
}
|
|||
|
|
|||
|
return temp;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the normal for the given time.
|
|||
|
/// </summary>
|
|||
|
/// <param name="time">The time</param>
|
|||
|
/// <returns>The normal.</returns>
|
|||
|
public Vector2 GetPositionNormal(float time)
|
|||
|
{
|
|||
|
float offsetTime = time + 0.0001f;
|
|||
|
|
|||
|
Vector2 a = GetPosition(time);
|
|||
|
Vector2 b = GetPosition(offsetTime);
|
|||
|
|
|||
|
Vector2 output, temp;
|
|||
|
|
|||
|
Vector2.Subtract(ref a, ref b, out temp);
|
|||
|
|
|||
|
#if (XBOX360 || WINDOWS_PHONE)
|
|||
|
output = new Vector2();
|
|||
|
#endif
|
|||
|
output.X = -temp.Y;
|
|||
|
output.Y = temp.X;
|
|||
|
|
|||
|
Vector2.Normalize(ref output, out output);
|
|||
|
|
|||
|
return output;
|
|||
|
}
|
|||
|
|
|||
|
public void Add(Vector2 point)
|
|||
|
{
|
|||
|
ControlPoints.Add(point);
|
|||
|
_deltaT = 1f / (ControlPoints.Count - 1);
|
|||
|
}
|
|||
|
|
|||
|
public void Remove(Vector2 point)
|
|||
|
{
|
|||
|
ControlPoints.Remove(point);
|
|||
|
_deltaT = 1f / (ControlPoints.Count - 1);
|
|||
|
}
|
|||
|
|
|||
|
public void RemoveAt(int index)
|
|||
|
{
|
|||
|
ControlPoints.RemoveAt(index);
|
|||
|
_deltaT = 1f / (ControlPoints.Count - 1);
|
|||
|
}
|
|||
|
|
|||
|
public float GetLength()
|
|||
|
{
|
|||
|
List<Vector2> verts = GetVertices(ControlPoints.Count * 25);
|
|||
|
float length = 0;
|
|||
|
|
|||
|
for (int i = 1; i < verts.Count; i++)
|
|||
|
{
|
|||
|
length += Vector2.Distance(verts[i - 1], verts[i]);
|
|||
|
}
|
|||
|
|
|||
|
if (Closed)
|
|||
|
length += Vector2.Distance(verts[ControlPoints.Count - 1], verts[0]);
|
|||
|
|
|||
|
return length;
|
|||
|
}
|
|||
|
|
|||
|
public List<Vector3> SubdivideEvenly(int divisions)
|
|||
|
{
|
|||
|
List<Vector3> verts = new List<Vector3>();
|
|||
|
|
|||
|
float length = GetLength();
|
|||
|
|
|||
|
float deltaLength = length / divisions + 0.001f;
|
|||
|
float t = 0.000f;
|
|||
|
|
|||
|
// we always start at the first control point
|
|||
|
Vector2 start = ControlPoints[0];
|
|||
|
Vector2 end = GetPosition(t);
|
|||
|
|
|||
|
// increment t until we are at half the distance
|
|||
|
while (deltaLength * 0.5f >= Vector2.Distance(start, end))
|
|||
|
{
|
|||
|
end = GetPosition(t);
|
|||
|
t += 0.0001f;
|
|||
|
|
|||
|
if (t >= 1f)
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
start = end;
|
|||
|
|
|||
|
// for each box
|
|||
|
for (int i = 1; i < divisions; i++)
|
|||
|
{
|
|||
|
Vector2 normal = GetPositionNormal(t);
|
|||
|
float angle = (float)Math.Atan2(normal.Y, normal.X);
|
|||
|
|
|||
|
verts.Add(new Vector3(end, angle));
|
|||
|
|
|||
|
// until we reach the correct distance down the curve
|
|||
|
while (deltaLength >= Vector2.Distance(start, end))
|
|||
|
{
|
|||
|
end = GetPosition(t);
|
|||
|
t += 0.00001f;
|
|||
|
|
|||
|
if (t >= 1f)
|
|||
|
break;
|
|||
|
}
|
|||
|
if (t >= 1f)
|
|||
|
break;
|
|||
|
|
|||
|
start = end;
|
|||
|
}
|
|||
|
return verts;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|