Simple Bezier Curve System

From XNAWiki
Jump to: navigation, search


This is a simple Bezier curve system I wrote up. It lets you give it a list of BezierPoints as control points. Then it generates a BezierVertex array that you can use for rendering the curve. It allows variable subdivision of the curves so you can balance performance and quality.

public class BezierCurve
{
	List<BezierPoint> points = new List<BezierPoint>();
 
	int verticesBetweenPoints = 100;
	BezierVertex[] vertexPoints;
	int lastNumPoints = -1;
	bool invalidateCurve = true;
	bool isLoop = false;
	BoundingBox bounds;
 
	public List<BezierPoint> Points
	{
		get { return points; }
	}
 
	public bool IsLoop
	{
		get { return isLoop; }
		set 
		{
			if (isLoop != value)
			{
				isLoop = value;
				invalidateCurve = true;
			}
		}
	}
 
	public BezierVertex[] VertexPoints
	{
		get { return vertexPoints; }
	}
 
	public int VerticesBetweenPoints
	{
		get { return verticesBetweenPoints; }
		set
		{
			if (verticesBetweenPoints != value)
			{
				verticesBetweenPoints = value;
				invalidateCurve = true;
			}
		}
	}
 
	public void Update()
	{
		//if there are new points, we have to scrap the whole curve
		if (lastNumPoints != points.Count)
		{
			invalidateCurve = true;
			lastNumPoints = points.Count;
		}
 
		Recompute();
	}
 
	void Recompute()
	{
		if (points.Count < 2)
			return;
 
		int numPoints = (isLoop) ? points.Count : points.Count - 1;
 
		if (invalidateCurve)
		{
			vertexPoints = new BezierVertex[numPoints * verticesBetweenPoints + 1];
			invalidateCurve = false;
		}
 
		float stepSize = 1f / (float)verticesBetweenPoints;
 
		Vector3 min = new Vector3(float.PositiveInfinity);
		Vector3 max = new Vector3(float.NegativeInfinity);
 
		min.Z = -1f;
		max.Z = 1f;
 
		for (int i = 0; i < numPoints; i++)
		{
			BezierPoint p1 = points[i % points.Count];
			BezierPoint p2 = points[(i + 1) % points.Count];
 
			int j = 0;
			for (float a = 0f; a <= 1f; a += stepSize)
			{
				//From http://freespace.virgin.net/hugo.elias/graphics/x_bezier.htm:
				//a = i
				//b = 1 - i
				//point = A*b3 + 3*B*b2*a + 3C*b*a2 + D*a3
 
				float b = 1f - a;
				Vector2 point =
					p1.Position * (float)Math.Pow(b, 3f) +
					3f * p1.NextControlPoint * (float)Math.Pow(b, 2f) * a +
					3f * p2.PreviousControlPoint * b * (float)Math.Pow(a, 2f) +
					p2.Position * (float)Math.Pow(a, 3f);
 
				if (point.X < min.X)
					min.X = point.X;
				if (point.X > max.X)
					max.X = point.X;
				if (point.Y < min.Y)
					min.Y = point.Y;
				if (point.Y > max.Y)
					max.Y = point.Y;
 
				vertexPoints[i * verticesBetweenPoints + j].Position = 
                                        new Vector3(point, 0f);
				vertexPoints[i * verticesBetweenPoints + j].Color = 
                                        Color.White.ToVector4();
				j++;
			}
		}
 
		bounds = new BoundingBox(min, max);
 
		for (int i = 1; i < vertexPoints.Length - 1; i++)
		{
			Vector3 p1 = vertexPoints[i - 1].Position;
			Vector3 p2 = vertexPoints[i].Position;
			Vector3 p3 = vertexPoints[i + 1].Position;
 
			Vector3 n1 = new Vector3(-(p2 - p1).Y, (p2 - p1).X, 0f);
			Vector3 n2 = new Vector3(-(p3 - p2).Y, (p3 - p2).X, 0f);
 
			vertexPoints[i].Normal = Vector3.Normalize((n1 + n2) / 2f);
		}
 
		//set the end point normals just matching the next inward normals
		vertexPoints[0].Normal = vertexPoints[1].Normal;
 
		if (isLoop)
			vertexPoints[vertexPoints.Length - 1].Normal = vertexPoints[1].Normal;
		else
			vertexPoints[vertexPoints.Length - 1].Normal = 
				vertexPoints[vertexPoints.Length - 2].Normal;
	}
}
 
public class BezierPoint
{
	Vector2 position;
	Vector2 nextControl;
	Vector2 prevControl;
 
	BezierCurve curve;
	bool updateCurve = true;
 
	public Vector2 Position
	{
		get { return position; }
		set
		{
			position = value;
			updateCurve = true;
		}
	}
 
	public Vector2 NextControlPoint
	{
		get { return nextControl; }
		set
		{
			nextControl = value;
			updateCurve = true;
		}
	}
 
	public Vector2 PreviousControlPoint
	{
		get { return prevControl; }
		set
		{
			prevControl = value;
			updateCurve = true;
		}
	}
 
	public BezierCurve Curve
	{
		get { return curve; }
	}
 
	public bool NeedsRecomputing
	{
		get { return updateCurve; }
		internal set { updateCurve = value; }
	}
 
	public BezierPoint(
		Vector2 position, 
		Vector2 nextControl, 
		Vector2 prevControl, 
		BezierCurve curve)
	{
		this.position = position;
		this.nextControl = nextControl;
		this.prevControl = prevControl;
		this.curve = curve;
	}
}
 
[Serializable]
public struct BezierVertex
{
	public Vector3 Position;
	public Vector3 Normal;
	public Vector4 Color;
 
	public static readonly int SizeInBytes = 36;
 
	public override string ToString()
	{
		return string.Format("P: {0}, N: {1}, C: {2}", Position, Normal, Color);
	}
 
	public override bool Equals(object obj)
	{
		if (obj == null)
			return false;
 
		return GetHashCode().Equals(obj.GetHashCode());
	}
 
	public override int GetHashCode()
	{
		return Position.GetHashCode() | Normal.GetHashCode() | Color.GetHashCode();
	}
 
	public static readonly VertexElement[] VertexElements = {
		new VertexElement(
			0, 
			0, 
			VertexElementFormat.Vector3, 
			VertexElementMethod.Default, 
			VertexElementUsage.Position, 
			0),
		new VertexElement(
			0,
			12, 
			VertexElementFormat.Vector3, 
			VertexElementMethod.Default, 
			VertexElementUsage.Normal, 0),
		new VertexElement(
			0, 
			24, 
			VertexElementFormat.Vector4, 
			VertexElementMethod.Default, 
			VertexElementUsage.Color, 
			0),
	};
}