diff --git a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
index a49ac7ef90..5e3e279deb 100644
--- a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
+++ b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Perspex.Media;
using Perspex.Platform;
+using Perspex.RenderHelpers;
namespace Perspex.Cairo.Media
{
@@ -12,6 +13,7 @@ namespace Perspex.Cairo.Media
public class StreamGeometryContextImpl : IStreamGeometryContextImpl
{
+ private Point _currentPoint;
public StreamGeometryContextImpl(Cairo.Path path = null)
{
@@ -27,24 +29,44 @@ namespace Perspex.Cairo.Media
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
+ ArcToHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
+ _currentPoint = point;
}
public void BeginFigure(Point startPoint, bool isFilled)
{
- if (this.Path == null)
- _context.MoveTo(startPoint.ToCairo());
+ if (this.Path == null)
+ {
+ _context.MoveTo(startPoint.ToCairo());
+ _currentPoint = startPoint;
+ }
}
public void BezierTo(Point point1, Point point2, Point point3)
{
- if (this.Path == null)
- _context.CurveTo(point1.ToCairo(), point2.ToCairo(), point3.ToCairo());
+ if (this.Path == null)
+ {
+ _context.CurveTo(point1.ToCairo(), point2.ToCairo(), point3.ToCairo());
+ _currentPoint = point3;
+ }
+ }
+
+ public void QuadTo(Point control, Point endPoint)
+ {
+ if (this.Path == null)
+ {
+ QuadBezierHelper.QuadTo(this, _currentPoint, control, endPoint);
+ _currentPoint = endPoint;
+ }
}
public void LineTo(Point point)
{
- if (this.Path == null)
- _context.LineTo(point.ToCairo());
+ if (this.Path == null)
+ {
+ _context.LineTo(point.ToCairo());
+ _currentPoint = point;
+ }
}
private readonly Cairo.Context _context;
diff --git a/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs b/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs
index dd42bddd46..88a5d0a7b9 100644
--- a/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs
+++ b/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs
@@ -38,6 +38,13 @@ namespace Perspex.Platform
/// The destination point for the end of the curve.
void BezierTo(Point point1, Point point2, Point point3);
+ ///
+ /// Draws a quadratic Bezier curve to the specified point
+ ///
+ /// Control point
+ /// DestinationPoint
+ void QuadTo(Point control, Point endPoint);
+
///
/// Draws a line to the specified point.
///
diff --git a/src/Perspex.SceneGraph/Point.cs b/src/Perspex.SceneGraph/Point.cs
index 923c2def71..d87c570e9c 100644
--- a/src/Perspex.SceneGraph/Point.cs
+++ b/src/Perspex.SceneGraph/Point.cs
@@ -128,6 +128,30 @@ namespace Perspex
return new Point(a._x - b.X, a._y - b.Y);
}
+ ///
+ /// Multiplies a point by a factor coordinate-wise
+ ///
+ /// Point to multiply
+ /// Factor
+ /// Points having its coordinates multiplied
+ public static Point operator *(Point p, double k) => new Point(p.X*k, p.Y*k);
+
+ ///
+ /// Multiplies a point by a factor coordinate-wise
+ ///
+ /// Point to multiply
+ /// Factor
+ /// Points having its coordinates multiplied
+ public static Point operator *(double k, Point p) => new Point(p.X*k, p.Y*k);
+
+ ///
+ /// Divides a point by a factor coordinate-wise
+ ///
+ /// Point to divide by
+ /// Factor
+ /// Points having its coordinates divided
+ public static Point operator /(Point p, double k) => new Point(p.X/k, p.Y/k);
+
///
/// Applies a matrix to a point.
///
diff --git a/src/Perspex.SceneGraph/Vector.cs b/src/Perspex.SceneGraph/Vector.cs
index 42732e4914..cc3a143c8b 100644
--- a/src/Perspex.SceneGraph/Vector.cs
+++ b/src/Perspex.SceneGraph/Vector.cs
@@ -3,6 +3,7 @@
using System;
using System.Globalization;
+using System.Xml.Linq;
namespace Perspex
{
@@ -51,6 +52,24 @@ namespace Perspex
return new Point(a._x, a._y);
}
+
+
+ ///
+ /// Calculates the dot product of two vectors
+ ///
+ /// First vector
+ /// Second vector
+ /// The dot product
+ public static double operator *(Vector a, Vector b)
+ {
+ return a.X*b.X + a.Y*b.Y;
+ }
+
+ ///
+ /// Length of the vector
+ ///
+ public double Length => Math.Sqrt(X*X + Y*Y);
+
///
/// Negates a vector.
///
diff --git a/src/Shared/RenderHelpers/ArcToHelper.cs b/src/Shared/RenderHelpers/ArcToHelper.cs
new file mode 100644
index 0000000000..180aeeb04e
--- /dev/null
+++ b/src/Shared/RenderHelpers/ArcToHelper.cs
@@ -0,0 +1,1118 @@
+// Copyright © 2003-2004, Luc Maisonobe
+// 2015 - Alexey Rozanov - Adaptations for Perspex and oval center computations
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with
+// or without modification, are permitted provided that
+// the following conditions are met:
+//
+// Redistributions of source code must retain the
+// above copyright notice, this list of conditions and
+// the following disclaimer.
+// Redistributions in binary form must reproduce the
+// above copyright notice, this list of conditions and
+// the following disclaimer in the documentation
+// and/or other materials provided with the
+// distribution.
+// Neither the names of spaceroots.org, spaceroots.com
+// nor the names of their contributors may be used to
+// endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+// C#/WPF/Perspex adaptation by Alexey Rozanov , 2015.
+// I do not mind if anyone would find this adaptation useful, but
+// please retain the above disclaimer made by the original class
+// author Luc Maisonobe. He worked really hard on this subject, so
+// please respect him by at least keeping the above disclaimer intact
+// if you use his code.
+//
+// Commented out some unused values calculations.
+// These are not supposed to be removed from the source code,
+// as these may be helpful for debugging.
+
+using System;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.RenderHelpers
+{
+ static class ArcToHelper
+ {
+ ///
+ /// This class represents an elliptical arc on a 2D plane.
+ ///
+ /// This class is adapted for use with WPF StreamGeometryContext, and needs to be created explicitly
+ /// for each particular arc.
+ ///
+ /// Some helpers
+ ///
+ /// It can handle ellipses which are not aligned with the x and y reference axes of the plane,
+ /// as well as their parts.
+ ///
+ /// Another improvement is that this class can handle degenerated cases like for example very
+ /// flat ellipses(semi-minor axis much smaller than semi-major axis) and drawing of very small
+ /// parts of such ellipses at very high magnification scales.This imply monitoring the drawing
+ /// approximation error for extremely small values.Such cases occur for example while drawing
+ /// orbits of comets near the perihelion.
+ ///
+ /// When the arc does not cover the complete ellipse, the lines joining the center of the
+ /// ellipse to the endpoints can optionally be included or not in the outline, hence allowing
+ /// to use it for pie-charts rendering. If these lines are not included, the curve is not
+ /// naturally closed.
+ ///
+ public sealed class EllipticalArc
+ {
+
+ private const double TwoPi = 2 * Math.PI;
+
+ ///
+ /// Coefficients for error estimation while using quadratic Bezier curves for approximation,
+ /// 0 ≤ b/a ≤ 0.25
+ ///
+ private static readonly double[][][] Coeffs2Low = {
+ new[]
+ {
+ new[] {3.92478, -13.5822, -0.233377, 0.0128206},
+ new[] {-1.08814, 0.859987, 3.62265E-4, 2.29036E-4},
+ new[] {-0.942512, 0.390456, 0.0080909, 0.00723895},
+ new[] {-0.736228, 0.20998, 0.0129867, 0.0103456}
+ },
+ new[]
+ {
+ new[] {-0.395018, 6.82464, 0.0995293, 0.0122198},
+ new[] {-0.545608, 0.0774863, 0.0267327, 0.0132482},
+ new[] {0.0534754, -0.0884167, 0.012595, 0.0343396},
+ new[] {0.209052, -0.0599987, -0.00723897, 0.00789976}
+ }
+ };
+
+ ///
+ /// Coefficients for error estimation while using quadratic Bezier curves for approximation,
+ /// 0.25 ≤ b/a ≤ 1
+ ///
+ private static readonly double[][][] Coeffs2High = {
+ new[]
+ {
+ new[] {0.0863805, -11.5595, -2.68765, 0.181224},
+ new[] {0.242856, -1.81073, 1.56876, 1.68544},
+ new[] {0.233337, -0.455621, 0.222856, 0.403469},
+ new[] {0.0612978, -0.104879, 0.0446799, 0.00867312}
+ },
+ new[]
+ {
+ new[] {0.028973, 6.68407, 0.171472, 0.0211706},
+ new[] {0.0307674, -0.0517815, 0.0216803, -0.0749348},
+ new[] {-0.0471179, 0.1288, -0.0781702, 2.0},
+ new[] {-0.0309683, 0.0531557, -0.0227191, 0.0434511}
+ }
+ };
+
+ ///
+ /// Safety factor to convert the "best" error approximation into a "max bound" error
+ ///
+ private static readonly double[] Safety2 = { 0.02, 2.83, 0.125, 0.01 };
+
+ ///
+ /// Coefficients for error estimation while using cubic Bezier curves for approximation,
+ /// 0.25 ≤ b/a ≤ 1
+ ///
+ private static readonly double[][][] Coeffs3Low = {
+ new[]
+ {
+ new[] {3.85268, -21.229, -0.330434, 0.0127842},
+ new[] {-1.61486, 0.706564, 0.225945, 0.263682},
+ new[] {-0.910164, 0.388383, 0.00551445, 0.00671814},
+ new[] {-0.630184, 0.192402, 0.0098871, 0.0102527}
+ },
+ new[]
+ {
+ new[] {-0.162211, 9.94329, 0.13723, 0.0124084},
+ new[] {-0.253135, 0.00187735, 0.0230286, 0.01264},
+ new[] {-0.0695069, -0.0437594, 0.0120636, 0.0163087},
+ new[] {-0.0328856, -0.00926032, -0.00173573, 0.00527385}
+ }
+ };
+
+ ///
+ /// Coefficients for error estimation while using cubic Bezier curves for approximation,
+ /// 0.25 ≤ b/a ≤ 1
+ ///
+ private static readonly double[][][] Coeffs3High = {
+ new[]
+ {
+ new[] {0.0899116, -19.2349, -4.11711, 0.183362},
+ new[] {0.138148, -1.45804, 1.32044, 1.38474},
+ new[] {0.230903, -0.450262, 0.219963, 0.414038},
+ new[] {0.0590565, -0.101062, 0.0430592, 0.0204699}
+ },
+ new[]
+ {
+ new[] {0.0164649, 9.89394, 0.0919496, 0.00760802},
+ new[] {0.0191603, -0.0322058, 0.0134667, -0.0825018},
+ new[] {0.0156192, -0.017535, 0.00326508, -0.228157},
+ new[] {-0.0236752, 0.0405821, -0.0173086, 0.176187}
+ }
+ };
+ ///
+ /// Safety factor to convert the "best" error approximation into a "max bound" error
+ ///
+ private static readonly double[] Safety3 = { 0.0010, 4.98, 0.207, 0.0067 };
+
+ ///
+ /// Abscissa of the center of the ellipse
+ ///
+ internal double Cx;
+ ///
+ /// Ordinate of the center of the ellipse
+ ///
+ internal double Cy;
+ ///
+ /// Semi-major axis
+ ///
+ internal double A;
+ ///
+ /// Semi-minor axis
+ ///
+ internal double B;
+ ///
+ /// Orientation of the major axis with respect to the x axis
+ ///
+ internal double Theta;
+ ///
+ /// Pre-calculated cosine value for the major-axis-to-X orientation (Theta)
+ ///
+ private readonly double _cosTheta;
+ ///
+ /// Pre-calculated sine value for the major-axis-to-X orientation (Theta)
+ ///
+ private readonly double _sinTheta;
+ ///
+ /// Start angle of the arc
+ ///
+ internal double Eta1;
+ ///
+ /// End angle of the arc
+ ///
+ internal double Eta2;
+ ///
+ /// Abscissa of the start point
+ ///
+ internal double X1;
+ ///
+ /// Ordinate of the start point
+ ///
+ internal double Y1;
+ ///
+ /// Abscissa of the end point
+ ///
+ internal double X2;
+ ///
+ /// Ordinate of the end point
+ ///
+ internal double Y2;
+ ///
+ /// Abscissa of the first focus
+ ///
+ internal double FirstFocusX;
+ ///
+ /// Ordinate of the first focus
+ ///
+ internal double FirstFocusY;
+ ///
+ /// Abscissa of the second focus
+ ///
+ internal double SecondFocusX;
+ ///
+ /// Ordinate of the second focus
+ ///
+ internal double SecondFocusY;
+ ///
+ /// Abscissa of the leftmost point of the arc
+ ///
+ private double _xLeft;
+ ///
+ /// Ordinate of the highest point of the arc
+ ///
+ private double _yUp;
+ ///
+ /// Horizontal width of the arc
+ ///
+ private double _width;
+ ///
+ /// Vertical height of the arc
+ ///
+ private double _height;
+ ///
+ /// Indicator for center to endpoints line inclusion
+ ///
+ internal bool IsPieSlice;
+ ///
+ /// Maximal degree for Bezier curve approximation
+ ///
+ private int _maxDegree;
+ ///
+ /// Default flatness for Bezier curve approximation
+ ///
+ private double _defaultFlatness;
+
+ ///
+ /// Indicator for semi-major axis significance (compared to semi-minor one).
+ /// Computed by dividing the (A-B) difference by the value of A.
+ /// This indicator is used for an early escape in intersection test
+ ///
+ internal double F;
+ ///
+ /// Indicator used for an early escape in intersection test
+ ///
+ internal double E2;
+ ///
+ /// Indicator used for an early escape in intersection test
+ ///
+ internal double G;
+ ///
+ /// Indicator used for an early escape in intersection test
+ ///
+ internal double G2;
+
+ ///
+ /// Builds an elliptical arc composed of the full unit circle around (0,0)
+ ///
+ public EllipticalArc()
+ {
+ Cx = 0;
+ Cy = 0;
+ A = 1;
+ B = 1;
+ Theta = 0;
+ Eta1 = 0;
+ Eta2 = TwoPi;
+ _cosTheta = 1;
+ _sinTheta = 0;
+ IsPieSlice = false;
+ _maxDegree = 3;
+ _defaultFlatness = 0.5;
+ ComputeFocii();
+ ComputeEndPoints();
+ ComputeBounds();
+ ComputeDerivedFlatnessParameters();
+ }
+
+ ///
+ /// Builds an elliptical arc from its canonical geometrical elements
+ ///
+ /// Center of the ellipse
+ /// Semi-major axis
+ /// Semi-minor axis
+ /// Orientation of the major axis with respect to the x axis
+ /// Start angle of the arc
+ /// End angle of the arc
+ /// If true, the lines between the center of the ellipse
+ /// and the endpoints are part of the shape (it is pie slice like)
+ public EllipticalArc(Point center, double a, double b, double theta, double lambda1, double lambda2,
+ bool isPieSlice) : this(center.X, center.Y, a, b, theta, lambda1,
+ lambda2, isPieSlice)
+ {
+ }
+ ///
+ /// Builds an elliptical arc from its canonical geometrical elements
+ ///
+ /// Abscissa of the center of the ellipse
+ /// Ordinate of the center of the ellipse
+ /// Semi-major axis
+ /// Semi-minor axis
+ /// Orientation of the major axis with respect to the x axis
+ /// Start angle of the arc
+ /// End angle of the arc
+ /// If true, the lines between the center of the ellipse
+ /// and the endpoints are part of the shape (it is pie slice like)
+ public EllipticalArc(double cx, double cy, double a, double b, double theta, double lambda1, double lambda2,
+ bool isPieSlice)
+ {
+ Cx = cx;
+ Cy = cy;
+ A = a;
+ B = b;
+ Theta = theta;
+ IsPieSlice = isPieSlice;
+ Eta1 = Math.Atan2(Math.Sin(lambda1) / b, Math.Cos(lambda1) / a);
+ Eta2 = Math.Atan2(Math.Sin(lambda2) / b, Math.Cos(lambda2) / a);
+ _cosTheta = Math.Cos(theta);
+ _sinTheta = Math.Sin(theta);
+ _maxDegree = 3;
+ _defaultFlatness = 0.5; // half a pixel
+ Eta2 -= TwoPi * Math.Floor((Eta2 - Eta1) / TwoPi); //make sure we have eta1 <= eta2 <= eta1 + 2 PI
+ // the preceding correction fails if we have exactly eta2-eta1 == 2*PI
+ // it reduces the interval to zero length
+ if (lambda2 - lambda1 > Math.PI && Eta2 - Eta1 < Math.PI)
+ {
+ Eta2 += TwoPi;
+ }
+ ComputeFocii();
+ ComputeEndPoints();
+ ComputeBounds();
+ ComputeDerivedFlatnessParameters();
+ }
+ ///
+ /// Build a full ellipse from its canonical geometrical elements
+ ///
+ /// Center of the ellipse
+ /// Semi-major axis
+ /// Semi-minor axis
+ /// Orientation of the major axis with respect to the x axis
+ public EllipticalArc(Point center, double a, double b, double theta) : this(center.X, center.Y, a, b, theta)
+ {
+ }
+
+ ///
+ /// Build a full ellipse from its canonical geometrical elements
+ ///
+ /// Abscissa of the center of the ellipse
+ /// Ordinate of the center of the ellipse
+ /// Semi-major axis
+ /// Semi-minor axis
+ /// Orientation of the major axis with respect to the x axis
+ public EllipticalArc(double cx, double cy, double a, double b, double theta)
+ {
+ Cx = cx;
+ Cy = cy;
+ A = a;
+ B = b;
+ Theta = theta;
+ IsPieSlice = false;
+ Eta1 = 0;
+ Eta2 = TwoPi;
+ _cosTheta = Math.Cos(theta);
+ _sinTheta = Math.Sin(theta);
+ _maxDegree = 3;
+ _defaultFlatness = 0.5; //half a pixel
+ ComputeFocii();
+ ComputeEndPoints();
+ ComputeBounds();
+ ComputeDerivedFlatnessParameters();
+ }
+
+ ///
+ /// Sets the maximal degree allowed for Bezier curve approximation.
+ ///
+ /// Maximal allowed degree (must be between 1 and 3)
+ /// Thrown if maxDegree is not between 1 and 3
+ public void SetMaxDegree(int maxDegree)
+ {
+ if (maxDegree < 1 || maxDegree > 3)
+ {
+ throw new ArgumentException(@"maxDegree must be between 1 and 3", nameof(maxDegree));
+ }
+ _maxDegree = maxDegree;
+ }
+
+ ///
+ /// Sets the default flatness for Bezier curve approximation
+ ///
+ /// default flatness (must be greater than 1e-10)
+ /// Thrown if defaultFlatness is lower than 1e-10
+ public void SetDefaultFlatness(double defaultFlatness)
+ {
+ if (defaultFlatness < 1.0E-10)
+ {
+ throw new ArgumentException(@"defaultFlatness must be greater than 1.0e-10", nameof(defaultFlatness));
+ }
+ _defaultFlatness = defaultFlatness;
+ }
+
+ ///
+ /// Computes the locations of the focii
+ ///
+ private void ComputeFocii()
+ {
+ double d = Math.Sqrt(A * A - B * B);
+ double dx = d * _cosTheta;
+ double dy = d * _sinTheta;
+ FirstFocusX = Cx - dx;
+ FirstFocusY = Cy - dy;
+ SecondFocusX = Cx + dx;
+ SecondFocusY = Cy + dy;
+ }
+
+ ///
+ /// Computes the locations of the endpoints
+ ///
+ private void ComputeEndPoints()
+ {
+ double aCosEta1 = A * Math.Cos(Eta1);
+ double bSinEta1 = B * Math.Sin(Eta1);
+ X1 = Cx + aCosEta1 * _cosTheta - bSinEta1 * _sinTheta;
+ Y1 = Cy + aCosEta1 * _sinTheta + bSinEta1 * _cosTheta;
+ double aCosEta2 = A * Math.Cos(Eta2);
+ double bSinEta2 = B * Math.Sin(Eta2);
+ X2 = Cx + aCosEta2 * _cosTheta - bSinEta2 * _sinTheta;
+ Y2 = Cy + aCosEta2 * _sinTheta + bSinEta2 * _cosTheta;
+ }
+
+ ///
+ /// Computes the bounding box
+ ///
+ private void ComputeBounds()
+ {
+ double bOnA = B / A;
+ double etaXMin;
+ double etaXMax;
+ double etaYMin;
+ double etaYMax;
+ if (Math.Abs(_sinTheta) < 0.1)
+ {
+ double tanTheta = _sinTheta / _cosTheta;
+ if (_cosTheta < 0)
+ {
+ etaXMin = -Math.Atan(tanTheta * bOnA);
+ etaXMax = etaXMin + Math.PI;
+ etaYMin = 0.5 * Math.PI - Math.Atan(tanTheta / bOnA);
+ etaYMax = etaYMin + Math.PI;
+ }
+ else
+ {
+ etaXMax = -Math.Atan(tanTheta * bOnA);
+ etaXMin = etaXMax - Math.PI;
+ etaYMax = 0.5 * Math.PI - Math.Atan(tanTheta / bOnA);
+ etaYMin = etaYMax - Math.PI;
+ }
+ }
+ else
+ {
+ double invTanTheta = _cosTheta / _sinTheta;
+ if (_sinTheta < 0)
+ {
+ etaXMax = 0.5 * Math.PI + Math.Atan(invTanTheta / bOnA);
+ etaXMin = etaXMax - Math.PI;
+ etaYMin = Math.Atan(invTanTheta * bOnA);
+ etaYMax = etaYMin + Math.PI;
+ }
+ else
+ {
+ etaXMin = 0.5 * Math.PI + Math.Atan(invTanTheta / bOnA);
+ etaXMax = etaXMin + Math.PI;
+ etaYMax = Math.Atan(invTanTheta * bOnA);
+ etaYMin = etaYMax - Math.PI;
+ }
+ }
+ etaXMin -= TwoPi * Math.Floor((etaXMin - Eta1) / TwoPi);
+ etaYMin -= TwoPi * Math.Floor((etaYMin - Eta1) / TwoPi);
+ etaXMax -= TwoPi * Math.Floor((etaXMax - Eta1) / TwoPi);
+ etaYMax -= TwoPi * Math.Floor((etaYMax - Eta1) / TwoPi);
+ _xLeft = etaXMin <= Eta2
+ ? Cx + A * Math.Cos(etaXMin) * _cosTheta - B * Math.Sin(etaXMin) * _sinTheta
+ : Math.Min(X1, X2);
+ _yUp = etaYMin <= Eta2 ? Cy + A * Math.Cos(etaYMin) * _sinTheta + B * Math.Sin(etaYMin) * _cosTheta : Math.Min(Y1, Y2);
+ _width = (etaXMax <= Eta2
+ ? Cx + A * Math.Cos(etaXMax) * _cosTheta - B * Math.Sin(etaXMax) * _sinTheta
+ : Math.Max(X1, X2)) - _xLeft;
+ _height = (etaYMax <= Eta2
+ ? Cy + A * Math.Cos(etaYMax) * _sinTheta + B * Math.Sin(etaYMax) * _cosTheta
+ : Math.Max(Y1, Y2)) - _yUp;
+ }
+
+ ///
+ /// Computes the flatness parameters used in intersection tests
+ ///
+ private void ComputeDerivedFlatnessParameters()
+ {
+ F = (A - B) / A;
+ E2 = F * (2.0 - F);
+ G = 1.0 - F;
+ G2 = G * G;
+ }
+
+ ///
+ /// Computes the value of a rational function.
+ /// This method handles rational functions where the numerator is quadratic
+ /// and the denominator is linear
+ ///
+ /// Abscissa for which the value should be computed
+ /// Coefficients array of the rational function
+ ///
+ private static double RationalFunction(double x, double[] c)
+ {
+ return (x * (x * c[0] + c[1]) + c[2]) / (x + c[3]);
+ }
+
+ ///
+ /// Estimate the approximation error for a sub-arc of the instance
+ ///
+ /// Degree of the Bezier curve to use (1, 2 or 3)
+ /// Start angle of the sub-arc
+ /// End angle of the sub-arc
+ /// Upper bound of the approximation error between the Bezier curve and the real ellipse
+ public double EstimateError(int degree, double etaA, double etaB)
+ {
+ if (degree < 1 || degree > _maxDegree)
+ throw new ArgumentException($"degree should be between {1} and {_maxDegree}", nameof(degree));
+ double eta = 0.5 * (etaA + etaB);
+ if (degree < 2)
+ {
+ //start point
+ double aCosEtaA = A * Math.Cos(etaA);
+ double bSinEtaA = B * Math.Sin(etaA);
+ double xA = Cx + aCosEtaA * _cosTheta - bSinEtaA * _sinTheta;
+ double yA = Cy + aCosEtaA * _sinTheta + bSinEtaA * _cosTheta;
+
+ //end point
+ double aCosEtaB = A * Math.Cos(etaB);
+ double bSinEtaB = B * Math.Sin(etaB);
+ double xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
+ double yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
+
+ //maximal error point
+ double aCosEta = A * Math.Cos(eta);
+ double bSinEta = B * Math.Sin(eta);
+ double x = Cx + aCosEta * _cosTheta - bSinEta * _sinTheta;
+ double y = Cy + aCosEta * _sinTheta + bSinEta * _cosTheta;
+
+ double dx = xB - xA;
+ double dy = yB - yA;
+
+ return Math.Abs(x * dy - y * dx + xB * yA - xA * yB) / Math.Sqrt(dx * dx + dy * dy);
+ }
+ else
+ {
+ double x = B / A;
+ double dEta = etaB - etaA;
+ double cos2 = Math.Cos(2 * eta);
+ double cos4 = Math.Cos(4 * eta);
+ double cos6 = Math.Cos(6 * eta);
+
+ // select the right coeficients set according to degree and b/a
+ double[][][] coeffs;
+ double[] safety;
+ if (degree == 2)
+ {
+ coeffs = x < 0.25 ? Coeffs2Low : Coeffs2High;
+ safety = Safety2;
+ }
+ else
+ {
+ coeffs = x < 0.25 ? Coeffs3Low : Coeffs3High;
+ safety = Safety3;
+ }
+ double c0 = RationalFunction(x, coeffs[0][0]) + cos2 * RationalFunction(x, coeffs[0][1]) +
+ cos4 * RationalFunction(x, coeffs[0][2]) + cos6 * RationalFunction(x,
+ coeffs[0][3]);
+ double c1 = RationalFunction(x, coeffs[1][0]) + cos2 * RationalFunction(x, coeffs[1][1]) +
+ cos4 * RationalFunction(x, coeffs[1][2]) + cos6 * RationalFunction(x,
+ coeffs[1][3]);
+ return RationalFunction(x, safety) * A * Math.Exp(c0 + c1 * dEta);
+ }
+ }
+
+ ///
+ /// Get the elliptical arc point for a given angular parameter
+ ///
+ /// Angular parameter for which point is desired
+ /// The desired elliptical arc point location
+ public Point PointAt(double lambda)
+ {
+ double eta = Math.Atan2(Math.Sin(lambda) / B, Math.Cos(lambda) / A);
+ double aCosEta = A * Math.Cos(eta);
+ double bSinEta = B * Math.Sin(eta);
+ Point p = new Point(Cx + aCosEta * _cosTheta - bSinEta * _sinTheta, Cy + aCosEta * _sinTheta + bSinEta * _cosTheta);
+ return p;
+ }
+
+ ///
+ /// Tests if the specified coordinates are inside the closed shape formed by this arc.
+ /// If this is not a pie, then a shape derived by adding a closing chord is considered.
+ ///
+ /// Abscissa of the test point
+ /// Ordinate of the test point
+ /// True if the specified coordinates are inside the closed shape of this arc
+ public bool Contains(double x, double y)
+ {
+ // position relative to the focii
+ double dx1 = x - FirstFocusX;
+ double dy1 = y - FirstFocusY;
+ double dx2 = x - SecondFocusX;
+ double dy2 = y - SecondFocusY;
+ if (dx1 * dx1 + dy1 * dy1 + dx2 * dx2 + dy2 * dy2 > 4 * A * A)
+ {
+ // the point is outside of the ellipse
+ return false;
+ }
+ if (IsPieSlice)
+ {
+ // check the location of the test point with respect to the
+ // angular sector counted from the centre of the ellipse
+ double dxC = x - Cx;
+ double dyC = y - Cy;
+ double u = dxC * _cosTheta + dyC * _sinTheta;
+ double v = dyC * _cosTheta - dxC * _sinTheta;
+ double eta = Math.Atan2(v / B, u / A);
+ eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
+ return eta <= Eta2;
+ }
+ // check the location of the test point with respect to the
+ // chord joining the start and end points
+ double dx = X2 - X1;
+ double dy = Y2 - Y1;
+ return x * dy - y * dx + X2 * Y1 - X1 * Y2 >= 0;
+ }
+
+ ///
+ /// Tests if a line segment intersects the arc
+ ///
+ /// abscissa of the first point of the line segment
+ /// ordinate of the first point of the line segment
+ /// abscissa of the second point of the line segment
+ /// ordinate of the second point of the line segment
+ /// true if the two line segments intersect
+ private bool IntersectArc(double xA, double yA, double xB, double yB)
+ {
+ double dx = xA - xB;
+ double dy = yA - yB;
+ double l = Math.Sqrt(dx * dx + dy * dy);
+ if (l < 1.0E-10 * A)
+ {
+ // too small line segment, we consider it doesn't intersect anything
+ return false;
+ }
+ double cz = (dx * _cosTheta + dy * _sinTheta) / l;
+ double sz = (dy * _cosTheta - dx * _sinTheta) / l;
+
+ // express position of the first point in canonical frame
+ dx = xA - Cx;
+ dy = yA - Cy;
+ double u = dx * _cosTheta + dy * _sinTheta;
+ double v = dy * _cosTheta - dx * _sinTheta;
+ double u2 = u * u;
+ double v2 = v * v;
+ double g2U2Ma2 = G2 * (u2 - A * A);
+ //double g2U2Ma2Mv2 = g2U2Ma2 - v2;
+ double g2U2Ma2Pv2 = g2U2Ma2 + v2;
+
+ // compute intersections with the ellipse along the line
+ // as the roots of a 2nd degree polynom : c0 k^2 - 2 c1 k + c2 = 0
+ double c0 = 1.0 - E2 * cz * cz;
+ double c1 = G2 * u * cz + v * sz;
+ double c2 = g2U2Ma2Pv2;
+ double c12 = c1 * c1;
+ double c0C2 = c0 * c2;
+ if (c12 < c0C2)
+ {
+ // the line does not intersect the ellipse at all
+ return false;
+ }
+ double k = c1 >= 0 ? (c1 + Math.Sqrt(c12 - c0C2)) / c0 : c2 / (c1 - Math.Sqrt(c12 - c0C2));
+ if (k >= 0 && k <= l)
+ {
+ double uIntersect = u - k * cz;
+ double vIntersect = v - k * sz;
+ double eta = Math.Atan2(vIntersect / B, uIntersect / A);
+ eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
+ if (eta <= Eta2)
+ {
+ return true;
+ }
+ }
+ k = c2 / (k * c0);
+ if (k >= 0 && k <= l)
+ {
+ double uIntersect = u - k * cz;
+ double vIntersect = v - k * sz;
+ double eta = Math.Atan2(vIntersect / B, uIntersect / A);
+ eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
+ if (eta <= Eta2)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Tests if two line segments intersect
+ ///
+ /// Abscissa of the first point of the first line segment
+ /// Ordinate of the first point of the first line segment
+ /// Abscissa of the second point of the first line segment
+ /// Ordinate of the second point of the first line segment
+ /// Abscissa of the first point of the second line segment
+ /// Ordinate of the first point of the second line segment
+ /// Abscissa of the second point of the second line segment
+ /// Ordinate of the second point of the second line segment
+ /// true if the two line segments intersect
+ private static bool Intersect(double x1, double y1, double x2, double y2, double xA, double yA, double xB,
+ double yB)
+ {
+ // elements of the equation of the (1, 2) line segment
+ double dx12 = x2 - x1;
+ double dy12 = y2 - y1;
+ double k12 = x2 * y1 - x1 * y2;
+ // elements of the equation of the (A, B) line segment
+ double dxAb = xB - xA;
+ double dyAb = yB - yA;
+ double kAb = xB * yA - xA * yB;
+ // compute relative positions of endpoints versus line segments
+ double pAvs12 = xA * dy12 - yA * dx12 + k12;
+ double pBvs12 = xB * dy12 - yB * dx12 + k12;
+ double p1VsAb = x1 * dyAb - y1 * dxAb + kAb;
+ double p2VsAb = x2 * dyAb - y2 * dxAb + kAb;
+
+ return pAvs12 * pBvs12 <= 0 && p1VsAb * p2VsAb <= 0;
+ }
+
+ ///
+ /// Tests if a line segment intersects the outline
+ ///
+ /// Abscissa of the first point of the line segment
+ /// Ordinate of the first point of the line segment
+ /// Abscissa of the second point of the line segment
+ /// Ordinate of the second point of the line segment
+ /// true if the two line segments intersect
+ private bool IntersectOutline(double xA, double yA, double xB, double yB)
+ {
+ if (IntersectArc(xA, yA, xB, yB))
+ {
+ return true;
+ }
+ if (IsPieSlice)
+ {
+ return Intersect(Cx, Cy, X1, Y1, xA, yA, xB, yB) || Intersect(Cx, Cy, X2, Y2, xA, yA, xB, yB);
+ }
+ return Intersect(X1, Y1, X2, Y2, xA, yA, xB, yB);
+ }
+
+ ///
+ /// Tests if the interior of a closed path derived from this arc entirely contains the specified rectangular area.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ /// Abscissa of the upper-left corner of the test rectangle
+ /// Ordinate of the upper-left corner of the test rectangle
+ /// Width of the test rectangle
+ /// Height of the test rectangle
+ /// true if the interior of a closed path derived from this arc entirely contains the specified rectangular area; false otherwise
+ public bool Contains(double x, double y, double w, double h)
+ {
+ double xPlusW = x + w;
+ double yPlusH = y + h;
+ return Contains(x, y) && Contains(xPlusW, y) && Contains(x, yPlusH) && Contains(xPlusW, yPlusH) &&
+ !IntersectOutline(x, y, xPlusW, y) && !IntersectOutline(xPlusW,
+ y, xPlusW, yPlusH) && !IntersectOutline(xPlusW, yPlusH, x, yPlusH) &&
+ !IntersectOutline(x, yPlusH, x, y);
+ }
+
+ ///
+ /// Tests if a specified Point2D is inside the boundary of a closed path derived from this arc.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ /// Test point
+ /// true if the specified point is inside a closed path derived from this arc
+ public bool Contains(Point p)
+ {
+ return Contains(p.X, p.Y);
+ }
+
+ ///
+ /// Tests if the interior of a closed path derived from this arc entirely contains the specified Rectangle2D.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ /// Test rectangle
+ /// True if the interior of a closed path derived from this arc entirely contains the specified Rectangle2D; false otherwise
+ public bool Contains(Rect r)
+ {
+ return Contains(r.X, r.Y, r.Width, r.Height);
+ }
+
+ ///
+ /// Returns an integer Rectangle that completely encloses the closed path derived from this arc.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ public Rect GetBounds()
+ {
+ return new Rect(_xLeft, _yUp, _width, _height);
+ }
+
+ ///
+ /// Builds the arc outline using given StreamGeometryContext and default (max) Bezier curve degree and acceptable error of half a pixel (0.5)
+ ///
+ /// A StreamGeometryContext to output the path commands to
+ public void BuildArc(IStreamGeometryContextImpl path)
+ {
+ BuildArc(path, _maxDegree, _defaultFlatness, true);
+ }
+
+ ///
+ /// Builds the arc outline using given StreamGeometryContext
+ ///
+ /// A StreamGeometryContext to output the path commands to
+ /// degree of the Bezier curve to use
+ /// acceptable error
+ /// if true, a new figure will be started in the specified StreamGeometryContext
+ public void BuildArc(IStreamGeometryContextImpl path, int degree, double threshold, bool openNewFigure)
+ {
+ if (degree < 1 || degree > _maxDegree)
+ throw new ArgumentException($"degree should be between {1} and {_maxDegree}", nameof(degree));
+
+ // find the number of Bezier curves needed
+ bool found = false;
+ int n = 1;
+ double dEta;
+ double etaB;
+ while (!found && n < 1024)
+ {
+ dEta = (Eta2 - Eta1) / n;
+ if (dEta <= 0.5 * Math.PI)
+ {
+ etaB = Eta1;
+ found = true;
+ for (int i = 0; found && i < n; ++i)
+ {
+ double etaA = etaB;
+ etaB += dEta;
+ found = EstimateError(degree, etaA, etaB) <= threshold;
+ }
+ }
+ n = n << 1;
+ }
+ dEta = (Eta2 - Eta1) / n;
+ etaB = Eta1;
+ double cosEtaB = Math.Cos(etaB);
+ double sinEtaB = Math.Sin(etaB);
+ double aCosEtaB = A * cosEtaB;
+ double bSinEtaB = B * sinEtaB;
+ double aSinEtaB = A * sinEtaB;
+ double bCosEtaB = B * cosEtaB;
+ double xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
+ double yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
+ double xBDot = -aSinEtaB * _cosTheta - bCosEtaB * _sinTheta;
+ double yBDot = -aSinEtaB * _sinTheta + bCosEtaB * _cosTheta;
+
+ /*
+ This controls the drawing in case of pies
+ if (openNewFigure)
+ {
+ if (IsPieSlice)
+ {
+ path.BeginFigure(new Point(Cx, Cy), false, false);
+ path.LineTo(new Point(xB, yB), true, true);
+ }
+ else
+ {
+ path.BeginFigure(new Point(xB, yB), false, false);
+ }
+ }
+ else
+ {
+ //path.LineTo(new Point(xB, yB), true, true);
+ }
+ */
+
+ //otherwise we're supposed to be already at the (xB,yB)
+
+ double t = Math.Tan(0.5 * dEta);
+ double alpha = Math.Sin(dEta) * (Math.Sqrt(4 + 3 * t * t) - 1) / 3;
+ for (int i = 0; i < n; ++i)
+ {
+ //double etaA = etaB;
+ double xA = xB;
+ double yA = yB;
+ double xADot = xBDot;
+ double yADot = yBDot;
+ etaB += dEta;
+ cosEtaB = Math.Cos(etaB);
+ sinEtaB = Math.Sin(etaB);
+ aCosEtaB = A * cosEtaB;
+ bSinEtaB = B * sinEtaB;
+ aSinEtaB = A * sinEtaB;
+ bCosEtaB = B * cosEtaB;
+ xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
+ yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
+ xBDot = -aSinEtaB * _cosTheta - bCosEtaB * _sinTheta;
+ yBDot = -aSinEtaB * _sinTheta + bCosEtaB * _cosTheta;
+ if (degree == 1)
+ {
+ path.LineTo(new Point(xB, yB));
+ }
+ else if (degree == 2)
+ {
+ double k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot);
+ path.QuadTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB));
+ }
+ else
+ {
+ path.BezierTo(
+ new Point(xA + alpha * xADot, yA + alpha * yADot),
+ new Point(xB - alpha * xBDot, yB - alpha * yBDot),
+ new Point(xB, yB)
+ );
+ }
+ }
+ if (IsPieSlice)
+ {
+ path.LineTo(new Point(Cx, Cy));
+ }
+ }
+
+ ///
+ /// Calculates the angle between two vectors
+ ///
+ /// Vector V1
+ /// Vector V2
+ /// The signed angle between v2 and v1
+ static double GetAngle(Vector v1, Vector v2)
+ {
+ var scalar = v1 * v2;
+ var angleSign = Math.Sign(v1.X * v2.Y - v1.Y * v2.X);
+ return angleSign * Math.Acos(scalar / (v1.Length * v2.Length));
+ }
+
+ ///
+ /// Simple matrix used for rotate transforms.
+ /// At some point I did not trust the WPF Matrix struct, and wrote my own simple one -_-
+ /// This is supposed to be replaced with proper WPF Matrices everywhere
+ ///
+ private struct SimpleMatrix
+ {
+ private readonly double _a, _b, _c, _d;
+
+ public SimpleMatrix(double a, double b, double c, double d)
+ {
+ _a = a;
+ _b = b;
+ _c = c;
+ _d = d;
+ }
+
+ public static Point operator *(SimpleMatrix m, Point p)
+ {
+ return new Point(m._a * p.X + m._b * p.Y, m._c * p.X + m._d * p.Y);
+ }
+ }
+
+ ///
+ /// ArcTo Helper for StreamGeometryContext
+ ///
+ /// Target path
+ /// Start point
+ /// End point
+ /// Ellipse radii
+ /// Ellipse theta (angle measured from the abscissa)
+ /// Large Arc Indicator
+ /// Clockwise direction flag
+ public static void BuildArc(IStreamGeometryContextImpl path, Point p1, Point p2, Size size, double theta, bool isLargeArc, bool clockwise)
+ {
+
+ // var orthogonalizer = new RotateTransform(-theta);
+ var orth = new SimpleMatrix(Math.Cos(theta), Math.Sin(theta), -Math.Sin(theta), Math.Cos(theta));
+ var rest = new SimpleMatrix(Math.Cos(theta), -Math.Sin(theta), Math.Sin(theta), Math.Cos(theta));
+
+ // var restorer = orthogonalizer.Inverse;
+ // if(restorer == null) throw new InvalidOperationException("Can't get a restorer!");
+
+ Point p1S = orth * (new Point((p1.X - p2.X) / 2, (p1.Y - p2.Y) / 2));
+
+ double rx = size.Width;
+ double ry = size.Height;
+ double rx2 = rx * rx;
+ double ry2 = ry * ry;
+ double y1S2 = p1S.Y * p1S.Y;
+ double x1S2 = p1S.X * p1S.X;
+
+ double multiplier = Math.Sqrt((rx2 * ry2 - rx2 * y1S2 - ry2 * x1S2) / (rx2 * y1S2 + ry2 * x1S2));
+ Point mulVec = new Point(rx * p1S.Y / ry, -ry * p1S.X / rx);
+
+ int sign = (clockwise != isLargeArc) ? 1 : -1;
+
+ Point cs = new Point(mulVec.X * multiplier * sign, mulVec.Y * multiplier * sign);
+
+ Vector translation = new Vector((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
+
+ Point c = rest * (cs) + translation;
+
+ // See "http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand
+ // how the ellipse center is calculated
+
+
+ // from here, W3C recommendations from the above link make less sense than Darth Vader pouring
+ // some sea water in a water filter while standing in the water confused
+
+ // Therefore, we are on our own with our task of finding out lambda1 and lambda2
+ // matching our points p1 and p2.
+
+ // Fortunately it is not so difficult now, when we already know the ellipse centre.
+
+ // We eliminate the offset, making our ellipse zero-centered, then we eliminate the theta,
+ // making its Y and X axes the same as global axes. Then we can easily get our angles using
+ // good old school formula for angles between vectors.
+
+ // We should remember that this class expects true angles, and not the t-values for ellipse equation.
+ // To understand how t-values are obtained, one should see Etas calculation in the constructor code.
+
+ var p1NoOffset = orth * (p1-c);
+ var p2NoOffset = orth * (p2-c);
+
+ // if the arc is drawn clockwise, we swap start and end points
+ var revisedP1 = clockwise ? p1NoOffset : p2NoOffset;
+ var revisedP2 = clockwise ? p2NoOffset : p1NoOffset;
+
+
+ var thetaStart = GetAngle(new Vector(1, 0), revisedP1);
+ var thetaEnd = GetAngle(new Vector(1, 0), revisedP2);
+
+
+ // Uncomment this to draw a pie
+ // path.LineTo(c, true, true);
+ // path.LineTo(clockwise ? p1 : p2, true,true);
+
+ path.LineTo(clockwise ? p1 : p2);
+ var arc = new EllipticalArc(c.X, c.Y, size.Width, size.Height, theta, thetaStart, thetaEnd, false);
+ arc.BuildArc(path, arc._maxDegree, arc._defaultFlatness, false);
+
+ //uncomment this to draw a pie
+ //path.LineTo(c, true, true);
+ }
+
+ ///
+ /// Tests if the interior of the closed path derived from this arc intersects the interior of a specified rectangular area.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ public bool Intersects(double x, double y, double w, double h)
+ {
+ double xPlusW = x + w;
+ double yPlusH = y + h;
+ return Contains(x, y) || Contains(xPlusW, y) || Contains(x, yPlusH) || Contains(xPlusW, yPlusH) ||
+ IntersectOutline(x, y, xPlusW, y) || IntersectOutline(xPlusW,
+ y, xPlusW, yPlusH) || IntersectOutline(xPlusW, yPlusH, x, yPlusH) ||
+ IntersectOutline(x, yPlusH, x, y);
+ }
+
+ ///
+ /// Tests if the interior of the closed path derived from this arc intersects the interior of a specified rectangular area.
+ /// The closed path is derived with respect to the IsPieSlice value.
+ ///
+ public bool Intersects(Rect r)
+ {
+ return Intersects(r.X, r.Y, r.Width, r.Height);
+ }
+ }
+
+ public static void ArcTo(IStreamGeometryContextImpl streamGeometryContextImpl, Point currentPoint, Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
+ {
+ EllipticalArc.BuildArc(streamGeometryContextImpl, currentPoint, point, size, rotationAngle*Math.PI/180,
+ isLargeArc,
+ sweepDirection == SweepDirection.Clockwise);
+ }
+ }
+}
diff --git a/src/Shared/RenderHelpers/QuadBezierHelper.cs b/src/Shared/RenderHelpers/QuadBezierHelper.cs
new file mode 100644
index 0000000000..8c6c9f3d63
--- /dev/null
+++ b/src/Shared/RenderHelpers/QuadBezierHelper.cs
@@ -0,0 +1,13 @@
+using Perspex.Platform;
+
+namespace Perspex.RenderHelpers
+{
+ static class QuadBezierHelper
+ {
+ public static void QuadTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint)
+ {
+ //(s, (s + 2c)/ 3, (e + 2c)/ 3, e)
+ context.BezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint);
+ }
+ }
+}
diff --git a/src/Shared/RenderHelpers/RenderHelpers.projitems b/src/Shared/RenderHelpers/RenderHelpers.projitems
index a34993265f..b38ae366d4 100644
--- a/src/Shared/RenderHelpers/RenderHelpers.projitems
+++ b/src/Shared/RenderHelpers/RenderHelpers.projitems
@@ -9,6 +9,8 @@
Perspex.RenderHelpers
+
+
\ No newline at end of file
diff --git a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
index a8021cdb80..bf72373423 100644
--- a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
@@ -47,6 +47,15 @@ namespace Perspex.Direct2D1.Media
});
}
+ public void QuadTo(Point control, Point dest)
+ {
+ _sink.AddQuadraticBezier(new QuadraticBezierSegment
+ {
+ Point1 = control.ToSharpDX(),
+ Point2 = dest.ToSharpDX()
+ });
+ }
+
public void LineTo(Point point)
{
_sink.AddLine(point.ToSharpDX());