Simple Bezier Curve System
From XNAWiki
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),
};
}