Skip to content

Commit

Permalink
Quadratic Bezier Curves!!
Browse files Browse the repository at this point in the history
  • Loading branch information
richardklafter committed Nov 10, 2011
1 parent 16738ba commit e04b1a7
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 11 deletions.
3 changes: 3 additions & 0 deletions JollyBit.Canvas.OpenGL.Demo/Program.cs
Expand Up @@ -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();
Expand Down
23 changes: 14 additions & 9 deletions JollyBit.Canvas/Canvas.cs
Expand Up @@ -14,6 +14,7 @@ public Canvas()
{
//Misc
InBatch = false;
CurveSmoothness = 0.5f;
//Lines
LineWidth = 1.0f;
LineCap = LineCapStyle.Butt;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 =>
{
Expand All @@ -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);
});
});
Expand Down Expand Up @@ -227,7 +232,7 @@ public enum LineJoinStyle
{
Bevel,
Round,
Miter
Miter,
}

public enum CompositeOperations
Expand Down
1 change: 1 addition & 0 deletions JollyBit.Canvas/JollyBit.Canvas.csproj
Expand Up @@ -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" />
Expand Down
127 changes: 127 additions & 0 deletions JollyBit.Canvas/Shapes/BezierCurves.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
}
}
14 changes: 12 additions & 2 deletions JollyBit.Canvas/Shapes/ComplexSubpath.cs
Expand Up @@ -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);
Expand All @@ -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]);
Expand All @@ -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()
Expand Down

0 comments on commit e04b1a7

Please sign in to comment.