Permalink
Browse files

Quadratic Bezier Curves!!

  • Loading branch information...
1 parent 16738ba commit e04b1a7da03788dccd02a86497edd7b669971ef0 @richardklafter richardklafter committed Nov 10, 2011
@@ -117,6 +117,9 @@ protected override void OnRenderFrame(FrameEventArgs e)
//Test 2
canvas.MoveTo(200, 200);
canvas.LineTo(250, 250);
+ canvas.QuadraticCurveTo(300, 50, 100, 200);
+
+ canvas.MoveTo(250, 250);
canvas.LineTo(100, 200);
canvas.Stroke();
canvas.EndBatch();
@@ -14,6 +14,7 @@ public Canvas()
{
//Misc
InBatch = false;
+ CurveSmoothness = 0.5f;
//Lines
LineWidth = 1.0f;
LineCap = LineCapStyle.Butt;
@@ -120,6 +121,12 @@ public void LineTo(float x, float y)
if (subpath == null) return;
subpath.LineTo(new Vector2(x, y).ApplyTransform(ref _tranMatrix));
}
+ public void QuadraticCurveTo(float cpx, float cpy, float x, float y)
+ {
+ ComplexSubpath subpath = ensureComplexSubpath(x, y);
+ if (subpath == null) return;
+ subpath.QuadraticCurveTo(new Vector2(cpx, cpy).ApplyTransform(ref _tranMatrix), new Vector2(x, y).ApplyTransform(ref _tranMatrix), CurveSmoothness);
+ }
protected ISubpath lastSubpath
{
get
@@ -138,6 +145,11 @@ public void Rect(float x, float y, float w, float h)
{
_subPaths.Add(new RectSubpath(x, y, w, h));
}
+
+ /// <summary>
+ /// Sets how accurately curves should be drawn. The default value is 0.5 which is smoother than the display device can display.
+ /// </summary>
+ public float CurveSmoothness { get; set; }
#endregion
#region Stroking
@@ -164,13 +176,6 @@ protected virtual void strokeLineSegment(LineSegment segment1, LineSegment segme
public virtual void Stroke()
{
- Func<Vector2, Vector2, Vector2, bool> isPointOnLeft =
- (lineStart, lineEnd, point) =>
- {
- //(Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax)
- return (lineEnd.X - lineStart.X) * (point.Y - lineStart.Y) - (lineEnd.Y - lineStart.Y) * (point.X - lineStart.X) >= 0;
- };
-
_subPaths.Apply(
subpath =>
{
@@ -186,7 +191,7 @@ public virtual void Stroke()
{
bool pointOnLeft = true;
if (segmentPair.Item1 != null && segmentPair.Item2 != null)
- pointOnLeft = isPointOnLeft(segmentPair.Item1.StartPoint, segmentPair.Item1.EndPoint, segmentPair.Item2.EndPoint);
+ pointOnLeft = BezierCurvesHelper.CalcWhatSideOfLinePointIsOn(segmentPair.Item1.StartPoint, segmentPair.Item1.EndPoint, segmentPair.Item2.EndPoint) > 0;
strokeLineSegment(segmentPair.Item1, segmentPair.Item2, LineJoinStyle.Miter,pointOnLeft);
});
});
@@ -227,7 +232,7 @@ public enum LineJoinStyle
{
Bevel,
Round,
- Miter
+ Miter,
}
public enum CompositeOperations
@@ -45,6 +45,7 @@
<Compile Include="Misc\Tuples.cs" />
<Compile Include="OpenTKExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Shapes\BezierCurves.cs" />
<Compile Include="Shapes\ComplexSubpath.cs" />
<Compile Include="Shapes\LineSegment.cs" />
<Compile Include="Shapes\RectSubpath.cs" />
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using OpenTK;
+
+namespace JollyBit.Canvas.Shapes
+{
+ /// <summary>
+ /// A static class with methods for createing bezier curves. Consult http://www.caffeineowl.com/graphics/2d/vectorial/bezierintro.html for a good bezier curves reference.
+ /// </summary>
+ public static class BezierCurvesHelper
+ {
+ #region Utility Functions
+ /// <summary>
+ /// Calculates the minimum distance between a line and a point.
+ /// </summary>
+ /// <param name="lineStart">The point at which the line starts</param>
+ /// <param name="lineEnd">The point at which the line ends</param>
+ /// <param name="point">The point</param>
+ /// <param name="whatSideOfLine">A negative number if point is on right side of line else returns a positive number. Return zero if the point is on the line.
+ /// The same value as CalcWhatSideOfLinePointIsOn would return.</param>
+ /// <returns></returns>
+ public static float CalcSquaredDistanceBetweenLineAndPoint(ref Vector2 lineStart, ref Vector2 lineEnd, ref Vector2 point, out float whatSideOfLine)
+ {
+ //http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm
+ whatSideOfLine = CalcWhatSideOfLinePointIsOn(lineStart, lineEnd, point);
+ Vector2 lineDiff;
+ Vector2.Subtract(ref lineEnd, ref lineStart, out lineDiff);
+ return (whatSideOfLine * whatSideOfLine) / lineDiff.LengthSquared;
+ }
+
+ /// <summary>
+ /// Calculates the minimum distance between a line and a point.
+ /// </summary>
+ /// <param name="lineStart">The point at which the line starts</param>
+ /// <param name="lineEnd">The point at which the line ends</param>
+ /// <param name="point">The point</param>
+ public static float CalcSquaredDistanceBetweenLineAndPoint(ref Vector2 lineStart, ref Vector2 lineEnd, ref Vector2 point)
+ {
+ float whatSideOfLine;
+ return CalcSquaredDistanceBetweenLineAndPoint(ref lineStart, ref lineEnd, ref point, out whatSideOfLine);
+ }
+
+ /// <summary>
+ /// Returns a negative number if point is on right side of line else returns a positive number. Return zero if the point is on the line.
+ /// </summary>
+ public static float CalcWhatSideOfLinePointIsOn(Vector2 lineStart, Vector2 lineEnd, Vector2 point)
+ {
+ return (lineEnd.X - lineStart.X) * (point.Y - lineStart.Y) - (lineEnd.Y - lineStart.Y) * (point.X - lineStart.X);
+ }
+
+ #endregion
+
+ #region QuadraticBezierCurve
+ public static IEnumerable<Vector2> CreateQuadraticBezierCurve(Vector2 anchorPoint1, Vector2 anchorPoint2, Vector2 controlPoint, float flatness)
+ {
+ flatness = flatness * flatness;
+ yield return anchorPoint1;
+ foreach (var item in _createQuadraticBezierCurve(anchorPoint1, anchorPoint2, controlPoint, flatness)) yield return item;
+ yield return anchorPoint2;
+ }
+ private static IEnumerable<Vector2> _createQuadraticBezierCurve(Vector2 anchorPoint1, Vector2 anchorPoint2, Vector2 controlPoint, float flatnessSquared)
+ {
+ //1) Check if flat enough
+ if (CalcSquaredDistanceBetweenLineAndPoint(ref anchorPoint1, ref anchorPoint2, ref controlPoint) > flatnessSquared)
+ {
+ //2) Compute nessary values
+ //segControl1 = (a1+c)/2
+ Vector2 segControl1;
+ Vector2.Add(ref anchorPoint1, ref controlPoint, out segControl1);
+ Vector2.Divide(ref segControl1, 2f, out segControl1);
+ //segControl2 = (a1+c)/2
+ Vector2 segControl2;
+ Vector2.Add(ref anchorPoint2, ref controlPoint, out segControl2);
+ Vector2.Divide(ref segControl2, 2f, out segControl2);
+ //segAnchor = (segControl1 + segControl2)/2 = (2c+a1+a2)/4
+ Vector2 segAnchor;
+ Vector2.Add(ref segControl1, ref segControl2, out segAnchor);
+ Vector2.Divide(ref segAnchor, 2f, out segAnchor);
+
+ //3) Recurse segments
+ foreach (var item in _createQuadraticBezierCurve(anchorPoint1, segAnchor, segControl1, flatnessSquared)) yield return item;
+ yield return segAnchor;
+ foreach (var item in _createQuadraticBezierCurve(segAnchor, anchorPoint2, segControl2, flatnessSquared)) yield return item;
+ }
+ }
+ #endregion
+
+ #region CubicBezierCurve
+ //public static IEnumerable<Vector2> CreateCubicBezierCurve(Vector2 anchorPoint1, Vector2 anchorPoint2, Vector2 controlPoint1, Vector2 controlPoint2, float flatness)
+ //{
+ // flatness = flatness * flatness;
+ // yield return anchorPoint1;
+ // foreach (var item in _createCubicBezierCurve(anchorPoint1, anchorPoint2, controlPoint1, controlPoint2, flatness)) yield return item;
+ // yield return anchorPoint2;
+ //}
+ //private static IEnumerable<Vector2> _createCubicBezierCurve(Vector2 anchorPoint1, Vector2 anchorPoint2, Vector2 controlPoint1, Vector2 controlPoint2, float flatnessSquared)
+ //{
+ // //1) Check if flat enough
+ // //if (CalcSquaredDistanceBetweenLineAndPoint(ref anchorPoint1, ref anchorPoint2, ref controlPoint) > flatnessSquared)
+ // //{
+ // //2) Compute nessary values
+ // //segControl1 = (a1+c)/2
+ // Vector2 segControl1;
+ // Vector2.Add(ref anchorPoint1, ref controlPoint, out segControl1);
+ // Vector2.Divide(ref segControl1, 2f, out segControl1);
+ // //segControl2 = (a1+c)/2
+ // Vector2 segControl2;
+ // Vector2.Add(ref anchorPoint2, ref controlPoint, out segControl2);
+ // Vector2.Divide(ref segControl2, 2f, out segControl2);
+ // //segAnchor = (a1 + 3•c1 + 3•c2 + a2)/8
+ // Vector2 segAnchor;
+ // Vector2.Multiply(ref controlPoint, 2f, out segAnchor);
+ // Vector2.Add(ref segAnchor, ref anchorPoint1, out segAnchor);
+ // Vector2.Add(ref segAnchor, ref anchorPoint2, out segAnchor);
+ // Vector2.Divide(ref segAnchor, 4f, out segAnchor);
+
+ // //3) Recurse segments
+ // foreach (var item in _createQuadraticBezierCurve(anchorPoint1, segAnchor, segControl1, flatnessSquared)) yield return item;
+ // yield return segAnchor;
+ // foreach (var item in _createQuadraticBezierCurve(segAnchor, anchorPoint2, segControl2, flatnessSquared)) yield return item;
+ // //}
+ //}
+ #endregion
+ }
+}
@@ -8,7 +8,7 @@ namespace JollyBit.Canvas.Shapes
{
public class ComplexSubpath : ISubpath
{
- private IList<Vector2> _vectors = new List<Vector2>();
+ private List<Vector2> _vectors = new List<Vector2>();
public ComplexSubpath(Vector2 startPoint)
{
_vectors.Add(startPoint);
@@ -24,6 +24,11 @@ public void LineTo(Vector2 point)
_vectors.Add(point);
}
+ public void QuadraticCurveTo(Vector2 controlPoint, Vector2 anchorPoint2, float flatness)
+ {
+ _vectors.AddRange(BezierCurvesHelper.CreateQuadraticBezierCurve(lastVector, anchorPoint2, controlPoint, flatness));
+ }
+
public void ClosePath()
{
if (!IsClosed) LineTo(_vectors[0]);
@@ -32,7 +37,12 @@ public void ClosePath()
public bool IsClosed
{
- get { return _vectors.Count > 1 && _vectors[0] == _vectors[_vectors.Count - 1]; }
+ get { return _vectors.Count > 1 && _vectors[0] == lastVector; }
+ }
+
+ private Vector2 lastVector
+ {
+ get { return _vectors[_vectors.Count - 1]; }
}
public IEnumerator<OpenTK.Vector2> GetEnumerator()

0 comments on commit e04b1a7

Please sign in to comment.