private static double[] IntersectT(PdfPath.BezierCurve bezierCurve, PdfPoint p1, PdfPoint p2) { // if the bounding boxes do not intersect, they cannot intersect var bezierBbox = bezierCurve.GetBoundingRectangle(); if (!bezierBbox.HasValue) { return(null); } if (bezierBbox.Value.Left > Math.Max(p1.X, p2.X) || Math.Min(p1.X, p2.X) > bezierBbox.Value.Right) { return(null); } if (bezierBbox.Value.Top < Math.Min(p1.Y, p2.Y) || Math.Max(p1.Y, p2.Y) < bezierBbox.Value.Bottom) { return(null); } double A = (p2.Y - p1.Y); double B = (p1.X - p2.X); double C = p1.X * (p1.Y - p2.Y) + p1.Y * (p2.X - p1.X); double alpha = bezierCurve.StartPoint.X * A + bezierCurve.StartPoint.Y * B; double beta = 3.0 * (bezierCurve.FirstControlPoint.X * A + bezierCurve.FirstControlPoint.Y * B); double gamma = 3.0 * (bezierCurve.SecondControlPoint.X * A + bezierCurve.SecondControlPoint.Y * B); double delta = bezierCurve.EndPoint.X * A + bezierCurve.EndPoint.Y * B; double a = -alpha + beta - gamma + delta; double b = 3 * alpha - 2 * beta + gamma; double c = -3 * alpha + beta; double d = alpha + C; var solution = SolveCubicEquation(a, b, c, d); return(solution.Where(s => !double.IsNaN(s)).Where(s => s >= -epsilon && (s - 1) <= epsilon).OrderBy(s => s).ToArray()); }
/// <summary> /// Algorithm to find a minimal bounding rectangle (MBR) such that the MBR corresponds to a rectangle /// with smallest possible area completely enclosing the polygon. /// <para>From 'A Fast Algorithm for Generating a Minimal Bounding Rectangle' by Lennert D. Den Boer.</para> /// </summary> /// <param name="polygon"> /// Polygon P is assumed to be both simple and convex, and to contain no duplicate (coincident) vertices. /// The vertices of P are assumed to be in strict cyclic sequential order, either clockwise or /// counter-clockwise relative to the origin P0. /// </param> internal static PdfRectangle ParametricPerpendicularProjection(IReadOnlyList <PdfPoint> polygon) { if (polygon == null || polygon.Count == 0) { throw new ArgumentException("ParametricPerpendicularProjection(): polygon cannot be null and must contain at least one point."); } if (polygon.Count < 4) { if (polygon.Count == 1) { return(new PdfRectangle(polygon[0], polygon[0])); } else if (polygon.Count == 2) { return(new PdfRectangle(polygon[0], polygon[1])); } else { PdfPoint p3 = polygon[0].Add(polygon[1].Subtract(polygon[2])); return(new PdfRectangle(p3, polygon[1], polygon[0], polygon[2])); } } PdfPoint[] MBR = new PdfPoint[0]; double Amin = double.MaxValue; double tmin = 1; double tmax = 0; double smax = 0; int j = 1; int k = 0; int l = -1; PdfPoint Q = new PdfPoint(); PdfPoint R0 = new PdfPoint(); PdfPoint R1 = new PdfPoint(); PdfPoint u = new PdfPoint(); int nv = polygon.Count; while (true) { var Pk = polygon[k]; PdfPoint v = polygon[j].Subtract(Pk); double r = 1.0 / v.DotProduct(v); for (j = 0; j < nv; j++) { if (j == k) { continue; } PdfPoint Pj = polygon[j]; u = Pj.Subtract(Pk); double t = u.DotProduct(v) * r; PdfPoint Pt = new PdfPoint(t * v.X + Pk.X, t * v.Y + Pk.Y); u = Pt.Subtract(Pj); double s = u.DotProduct(u); if (t < tmin) { tmin = t; R0 = Pt; } if (t > tmax) { tmax = t; R1 = Pt; } if (s > smax) { smax = s; Q = Pt; l = j; } } if (l == -1) { // All points are colinear - rectangle has no area (need more tests) var bottomLeft = polygon.OrderBy(p => p.X).ThenBy(p => p.Y).First(); var topRight = polygon.OrderByDescending(p => p.Y).OrderByDescending(p => p.X).First(); return(new PdfRectangle(bottomLeft, topRight, bottomLeft, topRight)); } PdfPoint PlMinusQ = polygon[l].Subtract(Q); PdfPoint R2 = R1.Add(PlMinusQ); PdfPoint R3 = R0.Add(PlMinusQ); u = R1.Subtract(R0); double A = u.DotProduct(u) * smax; if (A < Amin) { Amin = A; MBR = new[] { R0, R1, R2, R3 }; } k++; j = k; if (j == nv) { j = 0; } if (k == nv) { break; } } return(new PdfRectangle(MBR[2], MBR[3], MBR[1], MBR[0])); }
/// <summary> /// Whether the line segment contains the point. /// </summary> public static bool Contains(this PdfPath.Line line, PdfPoint point) { return(Contains(line.From, line.To, point)); }
/// <summary> /// Whether the line segment contains the point. /// </summary> public static bool Contains(this PdfLine line, PdfPoint point) { return(Contains(line.Point1, line.Point2, point)); }
internal PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
/// <summary> /// Get the <see cref="PdfPoint"/> that is the intersection of two lines. /// </summary> public static PdfPoint?Intersect(this Line line, Line other) { // if the bounding boxes do not intersect, the lines cannot intersect var thisLineBbox = line.GetBoundingRectangle(); if (!thisLineBbox.HasValue) { return(null); } var lineBbox = other.GetBoundingRectangle(); if (!lineBbox.HasValue) { return(null); } if (!thisLineBbox.Value.IntersectsWith(lineBbox.Value)) { return(null); } var eq1 = GetSlopeIntercept(line.From, line.To); var eq2 = GetSlopeIntercept(other.From, other.To); if (double.IsNaN(eq1.Slope) && double.IsNaN(eq2.Slope)) { return(null); // both lines are vertical (hence parallel) } if (eq1.Slope == eq2.Slope) { return(null); // both lines are parallel } var intersection = new PdfPoint(); if (double.IsNaN(eq1.Slope)) { var x = eq1.Intercept; var y = eq2.Slope * x + eq2.Intercept; intersection = new PdfPoint(x, y); } else if (double.IsNaN(eq2.Slope)) { var x = eq2.Intercept; var y = eq1.Slope * x + eq1.Intercept; intersection = new PdfPoint(x, y); } else { var x = (eq2.Intercept - eq1.Intercept) / (eq1.Slope - eq2.Slope); var y = eq1.Slope * x + eq1.Intercept; intersection = new PdfPoint(x, y); } // check if the intersection point belongs to both segments // (for the moment we only know it belongs to both lines) if (!line.Contains(intersection)) { return(null); } if (!other.Contains(intersection)) { return(null); } return(intersection); }
/// <summary> /// Algorithm to find a minimal bounding rectangle (MBR) such that the MBR corresponds to a rectangle /// with smallest possible area completely enclosing the polygon. /// <para>From 'A Fast Algorithm for Generating a Minimal Bounding Rectangle' by Lennert D. Den Boer.</para> /// </summary> /// <param name="polygon"> /// Polygon P is assumed to be both simple and convex, and to contain no duplicate (coincident) vertices. /// The vertices of P are assumed to be in strict cyclic sequential order, either clockwise or /// counter-clockwise relative to the origin P0. /// </param> private static PdfRectangle ParametricPerpendicularProjection(IReadOnlyList <PdfPoint> polygon) { if (polygon == null || polygon.Count == 0) { throw new ArgumentException("ParametricPerpendicularProjection(): polygon cannot be null and must contain at least one point.", nameof(polygon)); } else if (polygon.Count == 1) { return(new PdfRectangle(polygon[0], polygon[0])); } else if (polygon.Count == 2) { return(new PdfRectangle(polygon[0], polygon[1])); } PdfPoint[] MBR = new PdfPoint[0]; double Amin = double.PositiveInfinity; int j = 1; int k = 0; PdfPoint Q = new PdfPoint(); PdfPoint R0 = new PdfPoint(); PdfPoint R1 = new PdfPoint(); PdfPoint u = new PdfPoint(); while (true) { PdfPoint Pk = polygon[k]; PdfPoint v = polygon[j].Subtract(Pk); double r = 1.0 / v.DotProduct(v); double tmin = 1; double tmax = 0; double smax = 0; int l = -1; for (j = 0; j < polygon.Count; j++) { PdfPoint Pj = polygon[j]; u = Pj.Subtract(Pk); double t = u.DotProduct(v) * r; PdfPoint Pt = new PdfPoint(t * v.X + Pk.X, t * v.Y + Pk.Y); u = Pt.Subtract(Pj); double s = u.DotProduct(u); if (t < tmin) { tmin = t; R0 = Pt; } if (t > tmax) { tmax = t; R1 = Pt; } if (s > smax) { smax = s; Q = Pt; l = j; } } if (l != -1) { PdfPoint PlMinusQ = polygon[l].Subtract(Q); PdfPoint R2 = R1.Add(PlMinusQ); PdfPoint R3 = R0.Add(PlMinusQ); u = R1.Subtract(R0); double A = u.DotProduct(u) * smax; if (A < Amin) { Amin = A; MBR = new[] { R0, R1, R2, R3 }; } } k++; j = k + 1; if (j == polygon.Count) { j = 0; } if (k == polygon.Count) { break; } } return(new PdfRectangle(MBR[2], MBR[3], MBR[1], MBR[0])); }
private static bool IntersectsWith(PdfPath.BezierCurve bezierCurve, PdfPoint p1, PdfPoint p2) { return(Intersect(bezierCurve, p1, p2).Length > 0); }
/// <summary> /// Create a new <see cref="Line"/>. /// </summary> public Line(PdfPoint from, PdfPoint to) { From = from; To = to; }
/// <summary> /// Create a Bezier curve at the provided points. /// </summary> public BezierCurve(PdfPoint startPoint, PdfPoint firstControlPoint, PdfPoint secondControlPoint, PdfPoint endPoint) { StartPoint = startPoint; FirstControlPoint = firstControlPoint; SecondControlPoint = secondControlPoint; EndPoint = endPoint; }
/// <summary> /// Create a new <see cref="Move"/> path command. /// </summary> /// <param name="location"></param> public Move(PdfPoint location) { Location = location; }
/// <summary> /// Algorithm to find a minimal bounding rectangle (MBR) such that the MBR corresponds to a rectangle /// with smallest possible area completely enclosing the polygon. /// <para>From 'A Fast Algorithm for Generating a Minimal Bounding Rectangle' by Lennert D. Den Boer.</para> /// </summary> /// <param name="polygon"> /// Polygon P is assumed to be both simple and convex, and to contain no duplicate (coincident) vertices. /// The vertices of P are assumed to be in strict cyclic sequential order, either clockwise or /// counter-clockwise relative to the origin P0. /// </param> private static PdfRectangle ParametricPerpendicularProjection(IReadOnlyList <PdfPoint> polygon) { if (polygon == null || polygon.Count == 0) { throw new ArgumentException("ParametricPerpendicularProjection(): polygon cannot be null and must contain at least one point.", nameof(polygon)); } else if (polygon.Count == 1) { return(new PdfRectangle(polygon[0], polygon[0])); } else if (polygon.Count == 2) { return(new PdfRectangle(polygon[0], polygon[1])); } double[] MBR = new double[8]; double Amin = double.PositiveInfinity; int j = 1; int k = 0; double QX = double.NaN; double QY = double.NaN; double R0X = double.NaN; double R0Y = double.NaN; double R1X = double.NaN; double R1Y = double.NaN; while (true) { PdfPoint Pk = polygon[k]; PdfPoint Pj = polygon[j]; double vX = Pj.X - Pk.X; double vY = Pj.Y - Pk.Y; double r = 1.0 / (vX * vX + vY * vY); double tmin = 1; double tmax = 0; double smax = 0; int l = -1; double uX; double uY; for (j = 0; j < polygon.Count; j++) { Pj = polygon[j]; uX = Pj.X - Pk.X; uY = Pj.Y - Pk.Y; double t = (uX * vX + uY * vY) * r; double PtX = t * vX + Pk.X; double PtY = t * vY + Pk.Y; uX = PtX - Pj.X; uY = PtY - Pj.Y; double s = uX * uX + uY * uY; if (t < tmin) { tmin = t; R0X = PtX; R0Y = PtY; } if (t > tmax) { tmax = t; R1X = PtX; R1Y = PtY; } if (s > smax) { smax = s; QX = PtX; QY = PtY; l = j; } } if (l != -1) { PdfPoint Pl = polygon[l]; double PlMinusQX = Pl.X - QX; double PlMinusQY = Pl.Y - QY; double R2X = R1X + PlMinusQX; double R2Y = R1Y + PlMinusQY; double R3X = R0X + PlMinusQX; double R3Y = R0Y + PlMinusQY; uX = R1X - R0X; uY = R1Y - R0Y; double A = (uX * uX + uY * uY) * smax; if (A < Amin) { Amin = A; MBR = new[] { R0X, R0Y, R1X, R1Y, R2X, R2Y, R3X, R3Y }; } } k++; j = k + 1; if (j == polygon.Count) { j = 0; } if (k == polygon.Count) { break; } } return(new PdfRectangle(new PdfPoint(MBR[4], MBR[5]), new PdfPoint(MBR[6], MBR[7]), new PdfPoint(MBR[2], MBR[3]), new PdfPoint(MBR[0], MBR[1]))); }
public PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
/// <summary> /// Whether the line formed by <paramref name="p11"/> and <paramref name="p12"/> /// intersects the line formed by <paramref name="p21"/> and <paramref name="p22"/>. /// </summary> public static bool IntersectsWith(PdfPoint p11, PdfPoint p12, PdfPoint p21, PdfPoint p22) { return((ccw(p11, p12, p21) != ccw(p11, p12, p22)) && (ccw(p21, p22, p11) != ccw(p21, p22, p12))); }
/// <summary> /// Return true if the points are in counter-clockwise order. /// </summary> /// <param name="point1">The first point.</param> /// <param name="point2">The second point.</param> /// <param name="point3">The third point.</param> private static bool ccw(PdfPoint point1, PdfPoint point2, PdfPoint point3) { return((point2.X - point1.X) * (point3.Y - point1.Y) > (point2.Y - point1.Y) * (point3.X - point1.X)); }
private static bool ParallelTo(PdfPoint p11, PdfPoint p12, PdfPoint p21, PdfPoint p22) { return(Math.Abs((p12.Y - p11.Y) * (p22.X - p21.X) - (p22.Y - p21.Y) * (p12.X - p11.X)) < epsilon); }
/// <summary> /// Get the dot product of both points. /// </summary> /// <param name="point1">The first point.</param> /// <param name="point2">The second point.</param> public static double DotProduct(this PdfPoint point1, PdfPoint point2) { return(point1.X * point2.X + point1.Y * point2.Y); }
private static PdfPoint[] Intersect(PdfPath.BezierCurve bezierCurve, PdfPoint p1, PdfPoint p2) { var ts = IntersectT(bezierCurve, p1, p2); if (ts == null || ts.Length == 0) { return(EmptyArray <PdfPoint> .Instance); } List <PdfPoint> points = new List <PdfPoint>(); foreach (var t in ts) { PdfPoint point = new PdfPoint( PdfPath.BezierCurve.ValueWithT(bezierCurve.StartPoint.X, bezierCurve.FirstControlPoint.X, bezierCurve.SecondControlPoint.X, bezierCurve.EndPoint.X, t), PdfPath.BezierCurve.ValueWithT(bezierCurve.StartPoint.Y, bezierCurve.FirstControlPoint.Y, bezierCurve.SecondControlPoint.Y, bezierCurve.EndPoint.Y, t)); if (Contains(p1, p2, point)) { points.Add(point); } } return(points.ToArray()); }
/// <summary> /// Get a point with the summed coordinates of both points. /// </summary> /// <param name="point1">The first point.</param> /// <param name="point2">The second point.</param> public static PdfPoint Add(this PdfPoint point1, PdfPoint point2) { return(new PdfPoint(point1.X + point2.X, point1.Y + point2.Y)); }
private static (double Slope, double Intercept) GetSlopeIntercept(PdfPoint point1, PdfPoint point2) { if (Math.Abs(point1.X - point2.X) > epsilon) { var slope = (point2.Y - point1.Y) / (point2.X - point1.X); var intercept = point2.Y - slope * point2.X; return(slope, intercept); } else { // vertical line special case return(double.NaN, point1.X); } }
/// <summary> /// Get a point with the substracted coordinates of both points. /// </summary> /// <param name="point1">The first point.</param> /// <param name="point2">The second point.</param> public static PdfPoint Subtract(this PdfPoint point1, PdfPoint point2) { return(new PdfPoint(point1.X - point2.X, point1.Y - point2.Y)); }
private static (double Slope, double Intercept) GetSlopeIntercept(PdfPoint point1, PdfPoint point2) { if ((point1.X - point2.X) != 0) // vertical line special case { var slope = (double)((point2.Y - point1.Y) / (point2.X - point1.X)); var intercept = (double)point2.Y - slope * (double)point2.X; return(slope, intercept); } else { return(double.NaN, (double)point1.X); } }
/// <summary> /// Create a new <see cref="PdfLine"/>. /// </summary> /// <param name="point1">First point of the line.</param> /// <param name="point2">Second point of the line.</param> public PdfLine(PdfPoint point1, PdfPoint point2) { Point1 = point1; Point2 = point2; }