diff --git a/JollyBit.Canvas.OpenGL.Demo/Program.cs b/JollyBit.Canvas.OpenGL.Demo/Program.cs
index f7d98c9..aaa261c 100644
--- a/JollyBit.Canvas.OpenGL.Demo/Program.cs
+++ b/JollyBit.Canvas.OpenGL.Demo/Program.cs
@@ -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();
diff --git a/JollyBit.Canvas/Canvas.cs b/JollyBit.Canvas/Canvas.cs
index 5dffad4..fa575ca 100644
--- a/JollyBit.Canvas/Canvas.cs
+++ b/JollyBit.Canvas/Canvas.cs
@@ -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));
}
+
+ ///
+ /// Sets how accurately curves should be drawn. The default value is 0.5 which is smoother than the display device can display.
+ ///
+ 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 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
diff --git a/JollyBit.Canvas/JollyBit.Canvas.csproj b/JollyBit.Canvas/JollyBit.Canvas.csproj
index b10f8db..046cf4e 100644
--- a/JollyBit.Canvas/JollyBit.Canvas.csproj
+++ b/JollyBit.Canvas/JollyBit.Canvas.csproj
@@ -45,6 +45,7 @@
+
diff --git a/JollyBit.Canvas/Shapes/BezierCurves.cs b/JollyBit.Canvas/Shapes/BezierCurves.cs
new file mode 100644
index 0000000..4178b4a
--- /dev/null
+++ b/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
+{
+ ///
+ /// 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.
+ ///
+ public static class BezierCurvesHelper
+ {
+ #region Utility Functions
+ ///
+ /// Calculates the minimum distance between a line and a point.
+ ///
+ /// The point at which the line starts
+ /// The point at which the line ends
+ /// The point
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// Calculates the minimum distance between a line and a point.
+ ///
+ /// The point at which the line starts
+ /// The point at which the line ends
+ /// The point
+ 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);
+ }
+
+ ///
+ /// 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.
+ ///
+ 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 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 _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 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 _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
+ }
+}
diff --git a/JollyBit.Canvas/Shapes/ComplexSubpath.cs b/JollyBit.Canvas/Shapes/ComplexSubpath.cs
index 1d2c307..2f5f670 100644
--- a/JollyBit.Canvas/Shapes/ComplexSubpath.cs
+++ b/JollyBit.Canvas/Shapes/ComplexSubpath.cs
@@ -8,7 +8,7 @@ namespace JollyBit.Canvas.Shapes
{
public class ComplexSubpath : ISubpath
{
- private IList _vectors = new List();
+ private List _vectors = new List();
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 GetEnumerator()