using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; using Microsoft.Xna.Framework; namespace FarseerPhysics.Common { //Contributed by Matthew Bettcher /// /// Path: /// Very similar to Vertices, but this /// class contains vectors describing /// control points on a Catmull-Rom /// curve. /// [XmlRoot("Path")] public class Path { /// /// All the points that makes up the curve /// [XmlElement("ControlPoints")] public List ControlPoints; private float _deltaT; /// /// Initializes a new instance of the class. /// public Path() { ControlPoints = new List(); } /// /// Initializes a new instance of the class. /// /// The vertices to created the path from. public Path(Vector2[] vertices) { ControlPoints = new List(vertices.Length); for (int i = 0; i < vertices.Length; i++) { Add(vertices[i]); } } /// /// Initializes a new instance of the class. /// /// The vertices to created the path from. public Path(IList vertices) { ControlPoints = new List(vertices.Count); for (int i = 0; i < vertices.Count; i++) { Add(vertices[i]); } } /// /// True if the curve is closed. /// /// true if closed; otherwise, false. [XmlElement("Closed")] public bool Closed { get; set; } /// /// Gets the next index of a controlpoint /// /// The index. /// public int NextIndex(int index) { if (index == ControlPoints.Count - 1) { return 0; } return index + 1; } /// /// Gets the previous index of a controlpoint /// /// The index. /// public int PreviousIndex(int index) { if (index == 0) { return ControlPoints.Count - 1; } return index - 1; } /// /// Translates the control points by the specified vector. /// /// The vector. public void Translate(ref Vector2 vector) { for (int i = 0; i < ControlPoints.Count; i++) ControlPoints[i] = Vector2.Add(ControlPoints[i], vector); } /// /// Scales the control points by the specified vector. /// /// The Value. public void Scale(ref Vector2 value) { for (int i = 0; i < ControlPoints.Count; i++) ControlPoints[i] = Vector2.Multiply(ControlPoints[i], value); } /// /// Rotate the control points by the defined value in radians. /// /// The amount to rotate by in radians. 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(); } /// /// Returns a set of points defining the /// curve with the specifed number of divisions /// between each control point. /// /// Number of divisions between each control point. /// 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; } /// /// Gets the normal for the given time. /// /// The time /// The normal. 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 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 SubdivideEvenly(int divisions) { List verts = new List(); 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; } } }