/// <summary> /// Adds an intersection <see cref="EventPoint"/> to the <see cref="Schedule"/> if the /// two specified <see cref="SweepLine"/> nodes indicate a line crossing.</summary> /// <param name="a"> /// The first <see cref="SweepLine"/> node to examine.</param> /// <param name="b"> /// The second <see cref="SweepLine"/> node to examine.</param> /// <param name="e"> /// The current <see cref="EventPoint"/> which receives a detected crossing that occurs /// exactly at the <see cref="Cursor"/>.</param> /// <remarks> /// If the <see cref="Schedule"/> already contains an <see cref="EventPoint"/> for the /// computed intersection, <b>AddCrossing</b> adds the indicated lines to the existing /// <see cref="EventPoint"/> if they are not already present.</remarks> private void AddCrossing(BraidedTreeNode <Int32, Int32> a, BraidedTreeNode <Int32, Int32> b, EventPoint e) { int aIndex = a.Key, bIndex = b.Key; LineIntersection c = Lines[aIndex].Intersect(Lines[bIndex]); // ignore crossings that involve only start or end points, // as those line events have been scheduled during initialization if ((c.First == LineLocation.Between && LineIntersection.Contains(c.Second)) || (LineIntersection.Contains(c.First) && c.Second == LineLocation.Between)) { // quit if crossing occurs before cursor PointD p = c.Shared.Value; int result = PointDComparerY.CompareExact(Cursor, p); if (result > 0) { return; } // update schedule if crossing occurs after cursor if (result < 0) { BraidedTreeNode <PointD, EventPoint> node; Schedule.TryAddNode(p, new EventPoint(p), out node); e = node._value; } // add crossing to current or scheduled event point e.TryAddLines(aIndex, c.First, bIndex, c.Second); } }
/// <summary> /// Normalizes all <see cref="Locations"/> to match the corresponding <see /// cref="Lines"/>.</summary> /// <param name="lines"> /// An <see cref="Array"/> containing all input <see cref="LineD"/> segments.</param> /// <remarks><para> /// <b>Normalize</b> inverts any <see cref="LineLocation.Start"/> or <see /// cref="LineLocation.End"/> values in the <see cref="Locations"/> collection whose /// corresponding <see cref="Lines"/> element indicates a <see cref="LineD"/> whose <see /// cref="LineD.Start"/> and <see cref="LineD.End"/> points have the opposite orientation. /// </para><para> /// Therefore, the <see cref="Locations"/> collection no longer reflects the sweep line /// direction, but rather the point at which the corresponding <see cref="LineD"/> /// touches the <see cref="Shared"/> coordinates. Call this method to prepare for output /// generation, after the <see cref="EventPoint"/> has been removed from the schedule of /// the sweep line algorithm.</para></remarks> internal void Normalize(LineD[] lines) { for (int i = 0; i < Locations.Count; i++) { LineLocation location = Locations[i]; const LineLocation startEnd = LineLocation.Start | LineLocation.End; // check for start & end point events if ((location & startEnd) != 0) { LineD line = lines[Lines[i]]; // report orientation of inverted line if (PointDComparerY.CompareExact(line.Start, line.End) > 0) { location ^= startEnd; Locations[i] = location; } } } }
/// <summary> /// Builds the <see cref="Schedule"/> and precomputes all <see cref="Slopes"/>. /// </summary> /// <param name="lines"> /// An <see cref="Array"/> containing the <see cref="LineD"/> segments to intersect. /// </param> /// <exception cref="ArgumentException"> /// <paramref name="lines"/> contains a <see cref="LineD"/> whose <see /// cref="LineD.Start"/> and <see cref="LineD.End"/> coordinates are equal.</exception> /// <exception cref="ArgumentNullException"> /// <paramref name="lines"/> is a null reference.</exception> private void BuildSchedule(LineD[] lines) { if (lines == null) { ThrowHelper.ThrowArgumentNullException("lines"); } Lines = lines; Positions = new double[lines.Length]; Slopes = new double[lines.Length]; for (int i = 0; i < lines.Length; i++) { LineD line = lines[i]; Slopes[i] = line.InverseSlope; int direction = PointDComparerY.CompareExact(line.Start, line.End); if (direction == 0) { ThrowHelper.ThrowArgumentExceptionWithFormat( "lines", Strings.ArgumentContainsEmpty, "LineD"); } // start & end point events use lexicographic ordering if (direction > 0) { line = line.Reverse(); } BraidedTreeNode <PointD, EventPoint> node; // add start point event for current line Schedule.TryAddNode(line.Start, new EventPoint(line.Start), out node); node._value.AddLine(i, LineLocation.Start); // add end point event for current line Schedule.TryAddNode(line.End, new EventPoint(line.End), out node); node._value.AddLine(i, LineLocation.End); } }
/// <summary> /// Finds the convex hull for the specified set of <see cref="PointD"/> coordinates. /// </summary> /// <param name="points"> /// An <see cref="Array"/> containing the <see cref="PointD"/> coordinates whose convex hull /// to find.</param> /// <returns> /// An <see cref="Array"/> containing the subset of the specified <paramref name="points"/> /// that represent the vertices of their convex hull.</returns> /// <exception cref="ArgumentNullOrEmptyException"> /// <paramref name="points"/> is a null reference or an empty array.</exception> /// <remarks><para> /// If the specified <paramref name="points"/> array contains only one or two elements, /// <b>ConvexHull</b> returns a new array containing the same elements. Points that are /// coincident or collinear with other hull vertices are always removed from the returned /// array, however. A <paramref name="points"/> array containing the same <see /// cref="PointD"/> twice will return an array containing that <see cref="PointD"/> once. /// </para><para> /// <b>ConvexHull</b> performs a Graham scan with an asymptotic runtime of O(n log n). This /// C# implementation was adapted from the <c>Graham</c> algorithm by Joseph O’Rourke, /// <em>Computational Geometry in C</em> (2nd ed.), Cambridge University Press 1998, p.72ff. /// </para></remarks> public static PointD[] ConvexHull(params PointD[] points) { if (points == null || points.Length == 0) { ThrowHelper.ThrowArgumentNullOrEmptyException("points"); } // handle trivial edge cases switch (points.Length) { case 1: return(new PointD[] { points[0] }); case 2: if (points[0] == points[1]) { goto case 1; } else { return new PointD[] { points[0], points[1] } }; } /* * Set index n to lowest vertex. Unlike O’Rourke, we immediately mark duplicates * of the current lowest vertex for deletion. This eliminates some corner cases * of multiple duplicates that are missed by ConvexHullVertexComparer.Compare. */ var p = new ConvexHullVertex[points.Length]; PointD pnv = points[0]; p[0] = new ConvexHullVertex(pnv, 0); int i, n = 0; for (i = 1; i < p.Length; i++) { PointD piv = points[i]; p[i] = new ConvexHullVertex(piv, i); int result = PointDComparerY.CompareExact(piv, pnv); if (result < 0) { n = i; pnv = piv; } else if (result == 0) { p[i].Delete = true; } } // move lowest vertex to index 0 if (n > 0) { var swap = p[0]; p[0] = p[n]; p[n] = swap; } // sort and mark collinear/coincident vertices for deletion var comparer = new ConvexHullVertexComparer(p[0]); Array.Sort(p, 1, p.Length - 1, comparer); // delete marked vertices (n is remaining count) for (i = 0, n = 0; i < p.Length; i++) { if (!p[i].Delete) { p[n++] = p[i]; } } // quit if only one unique vertex remains if (n == 1) { return new[] { p[0].Vertex } } ; // begin stack of convex hull vertices var top = p[1]; top.Next = p[0]; int hullCount = 2; // first two vertices are permanent, now examine others for (i = 2; i < n;) { ConvexHullVertex pi = p[i]; if (top.Next.Vertex.CrossProductLength(top.Vertex, pi.Vertex) > 0) { // push p[i] on stack pi.Next = top; top = pi; ++i; ++hullCount; } else { // pop top from stack top = top.Next; --hullCount; } } // convert vertex stack to point array PointD[] hull = new PointD[hullCount]; for (i = 0; i < hull.Length; i++) { hull[i] = top.Vertex; top = top.Next; } Debug.Assert(top == null); return(hull); }
/// <summary> /// Finds the intersection of the specified line segments, given the specified epsilon for /// coordinate comparisons.</summary> /// <param name="a"> /// The <see cref="LineD.Start"/> point of the first line segment.</param> /// <param name="b"> /// The <see cref="LineD.End"/> point of the first line segment.</param> /// <param name="c"> /// The <see cref="LineD.Start"/> point of the second line segment.</param> /// <param name="d"> /// The <see cref="LineD.End"/> point of the second line segment.</param> /// <param name="epsilon"> /// The maximum absolute difference at which coordinates and intermediate results should be /// considered equal. This value is always raised to a minium of 1e-10.</param> /// <returns> /// A <see cref="LineIntersection"/> instance that describes if and how the line segments /// from <paramref name="a"/> to <paramref name="b"/> and from <paramref name="c"/> to /// <paramref name="d"/> intersect.</returns> /// <remarks><para> /// <b>Find</b> is identical with the basic <see cref="Find(PointD, PointD, PointD, /// PointD)"/> overload but uses the specified <paramref name="epsilon"/> to compare /// coordinates and intermediate results. /// </para><para> /// <b>Find</b> always raises the specified <paramref name="epsilon"/> to a minimum of 1e-10 /// because the algorithm is otherwise too unstable, and would initiate multiple recursions /// with a greater epsilon anyway.</para></remarks> public static LineIntersection Find(PointD a, PointD b, PointD c, PointD d, double epsilon) { if (epsilon < 1e-10) { epsilon = 1e-10; } LineLocation first, second; double bax = b.X - a.X, bay = b.Y - a.Y; double dcx = d.X - c.X, dcy = d.Y - c.Y; // compute cross-products for all end points double d1 = (a.X - c.X) * dcy - (a.Y - c.Y) * dcx; double d2 = (b.X - c.X) * dcy - (b.Y - c.Y) * dcx; double d3 = (c.X - a.X) * bay - (c.Y - a.Y) * bax; double d4 = (d.X - a.X) * bay - (d.Y - a.Y) * bax; //Debug.Assert(d1 == c.CrossProductLength(a, d)); //Debug.Assert(d2 == c.CrossProductLength(b, d)); //Debug.Assert(d3 == a.CrossProductLength(c, b)); //Debug.Assert(d4 == a.CrossProductLength(d, b)); // check for collinear (but not parallel) lines if (Math.Abs(d1) <= epsilon && Math.Abs(d2) <= epsilon && Math.Abs(d3) <= epsilon && Math.Abs(d4) <= epsilon) { // find lexicographically first point where segments overlap if (PointDComparerY.CompareExact(c, d) < 0) { first = LocateCollinear(a, b, c, epsilon); if (Contains(first)) { return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear)); } first = LocateCollinear(a, b, d, epsilon); if (Contains(first)) { return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear)); } } else { first = LocateCollinear(a, b, d, epsilon); if (Contains(first)) { return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear)); } first = LocateCollinear(a, b, c, epsilon); if (Contains(first)) { return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear)); } } // collinear line segments without overlapping points return(new LineIntersection(LineRelation.Collinear)); } // check for divergent lines with end point intersection if (Math.Abs(d1) <= epsilon) { second = LocateCollinear(c, d, a, epsilon); return(new LineIntersection(a, LineLocation.Start, second, LineRelation.Divergent)); } if (Math.Abs(d2) <= epsilon) { second = LocateCollinear(c, d, b, epsilon); return(new LineIntersection(b, LineLocation.End, second, LineRelation.Divergent)); } if (Math.Abs(d3) <= epsilon) { first = LocateCollinear(a, b, c, epsilon); return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Divergent)); } if (Math.Abs(d4) <= epsilon) { first = LocateCollinear(a, b, d, epsilon); return(new LineIntersection(d, first, LineLocation.End, LineRelation.Divergent)); } // compute parameters of line equations double denom = dcx * bay - bax * dcy; if (Math.Abs(denom) <= epsilon) { return(new LineIntersection(LineRelation.Parallel)); } double snum = a.X * dcy - a.Y * dcx - c.X * d.Y + c.Y * d.X; double s = snum / denom; if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) { if (s < 0) { first = LineLocation.Before; } else if (s > 1) { first = LineLocation.After; } else { return(Find(a, b, c, d, 2 * epsilon)); } } else { if (s > 0 && s < 1) { first = LineLocation.Between; } else { return(Find(a, b, c, d, 2 * epsilon)); } } double tnum = c.Y * bax - c.X * bay + a.X * b.Y - a.Y * b.X; double t = tnum / denom; if ((d3 < 0 && d4 < 0) || (d3 > 0 && d4 > 0)) { if (t < 0) { second = LineLocation.Before; } else if (t > 1) { second = LineLocation.After; } else { return(Find(a, b, c, d, 2 * epsilon)); } } else { if (t > 0 && t < 1) { second = LineLocation.Between; } else { return(Find(a, b, c, d, 2 * epsilon)); } } PointD shared = new PointD(a.X + s * bax, a.Y + s * bay); /* * Epsilon comparisons of cross products (or line equation parameters) might miss * epsilon-close end point intersections of very long line segments. We compensate by * directly comparing the computed intersection point against the four end points. */ if (PointD.Equals(a, shared, epsilon)) { first = LineLocation.Start; } else if (PointD.Equals(b, shared, epsilon)) { first = LineLocation.End; } if (PointD.Equals(c, shared, epsilon)) { second = LineLocation.Start; } else if (PointD.Equals(d, shared, epsilon)) { second = LineLocation.End; } return(new LineIntersection(shared, first, second, LineRelation.Divergent)); }
/// <overloads> /// Finds the intersection of the specified line segments.</overloads> /// <summary> /// Finds the intersection of the specified line segments, using exact coordinate /// comparisons.</summary> /// <param name="a"> /// The <see cref="LineD.Start"/> point of the first line segment.</param> /// <param name="b"> /// The <see cref="LineD.End"/> point of the first line segment.</param> /// <param name="c"> /// The <see cref="LineD.Start"/> point of the second line segment.</param> /// <param name="d"> /// The <see cref="LineD.End"/> point of the second line segment.</param> /// <returns> /// A <see cref="LineIntersection"/> instance that describes if and how the line segments /// from <paramref name="a"/> to <paramref name="b"/> and from <paramref name="c"/> to /// <paramref name="d"/> intersect.</returns> /// <remarks><para> /// <b>Find</b> was adapted from the <c>Segments-Intersect</c> algorithm by Thomas H. Cormen /// et al., <em>Introduction to Algorithms</em> (3rd ed.), The MIT Press 2009, p.1018, for /// intersection testing; and from the <c>SegSegInt</c> and <c>ParallelInt</c> algorithms by /// Joseph O’Rourke, <em>Computational Geometry in C</em> (2nd ed.), Cambridge University /// Press 1998, p.224f, for line relationships and shared coordinates. /// </para><para> /// Cormen’s intersection testing first examines the <see cref="PointD.CrossProductLength"/> /// for each triplet of specified points. If that is insufficient, O’Rourke’s algorithm then /// examines the parameters of both line equations. This is mathematically redundant since /// O’Rourke’s algorithm alone should produce all desired information, but the combination /// of both algorithms proved much more resilient against misjudging line relationships due /// to floating-point inaccuracies. /// </para><para> /// Although most comparisons in this overload are exact, cross-product testing is always /// performed with a minimum epsilon of 1e-10. Moreover, <b>Find</b> will return the result /// of the other <see cref="Find(PointD, PointD, PointD, PointD, Double)"/> overload with an /// epsilon of 2e-10 if cross-product testing contradicts line equation testing. Subsequent /// contradictions result in further recursive calls, each time with a doubled epsilon, /// until an intersection can be determined without contradictions.</para></remarks> public static LineIntersection Find(PointD a, PointD b, PointD c, PointD d) { const double epsilon = 1e-10; LineLocation first, second; double bax = b.X - a.X, bay = b.Y - a.Y; double dcx = d.X - c.X, dcy = d.Y - c.Y; // compute cross-products for all end points double d1 = (a.X - c.X) * dcy - (a.Y - c.Y) * dcx; double d2 = (b.X - c.X) * dcy - (b.Y - c.Y) * dcx; double d3 = (c.X - a.X) * bay - (c.Y - a.Y) * bax; double d4 = (d.X - a.X) * bay - (d.Y - a.Y) * bax; //Debug.Assert(d1 == c.CrossProductLength(a, d)); //Debug.Assert(d2 == c.CrossProductLength(b, d)); //Debug.Assert(d3 == a.CrossProductLength(c, b)); //Debug.Assert(d4 == a.CrossProductLength(d, b)); /* * Some cross-products are zero: corresponding end point triplets are collinear. * * The infinite lines intersect on the corresponding end points. The lines are collinear * exactly if all cross-products are zero; otherwise, the lines are divergent and we * need to check whether the finite line segments also intersect on the end points. * * We always perform epsilon comparisons on cross-products, even in the exact overload, * because almost-zero cases are very frequent, especially for collinear lines. */ if (Math.Abs(d1) <= epsilon && Math.Abs(d2) <= epsilon && Math.Abs(d3) <= epsilon && Math.Abs(d4) <= epsilon) { // find lexicographically first point where segments overlap if (PointDComparerY.CompareExact(c, d) < 0) { first = LocateCollinear(a, b, c); if (Contains(first)) { return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear)); } first = LocateCollinear(a, b, d); if (Contains(first)) { return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear)); } } else { first = LocateCollinear(a, b, d); if (Contains(first)) { return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear)); } first = LocateCollinear(a, b, c); if (Contains(first)) { return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear)); } } // collinear line segments without overlapping points return(new LineIntersection(LineRelation.Collinear)); } // check for divergent lines with end point intersection if (Math.Abs(d1) <= epsilon) { second = LocateCollinear(c, d, a); return(new LineIntersection(a, LineLocation.Start, second, LineRelation.Divergent)); } if (Math.Abs(d2) <= epsilon) { second = LocateCollinear(c, d, b); return(new LineIntersection(b, LineLocation.End, second, LineRelation.Divergent)); } if (Math.Abs(d3) <= epsilon) { first = LocateCollinear(a, b, c); return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Divergent)); } if (Math.Abs(d4) <= epsilon) { first = LocateCollinear(a, b, d); return(new LineIntersection(d, first, LineLocation.End, LineRelation.Divergent)); } /* * All cross-products are non-zero: divergent or parallel lines. * * The lines and segments might intersect, but not on any end point. * Compute parameters of both line equations to determine intersections. * Zero denominator indicates parallel lines (but not collinear, see above). */ double denom = dcx * bay - bax * dcy; if (Math.Abs(denom) <= epsilon) { return(new LineIntersection(LineRelation.Parallel)); } /* * Compute position of intersection point relative to line segments, and also perform * sanity checks for floating-point inaccuracies. If a check fails, we cannot give a * reliable result at the current precision and must recurse with a greater epsilon. * * Cross-products have pairwise opposite signs exactly if the corresponding line segment * straddles the infinite extension of the other line segment, implying a line equation * parameter between zero and one. Pairwise identical signs imply a parameter less than * zero or greater than one. Parameters cannot be exactly zero or one, as that indicates * end point intersections which were already ruled out by cross-product testing. */ double snum = a.X * dcy - a.Y * dcx - c.X * d.Y + c.Y * d.X; double s = snum / denom; if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) { if (s < 0) { first = LineLocation.Before; } else if (s > 1) { first = LineLocation.After; } else { return(Find(a, b, c, d, 2 * epsilon)); } } else { if (s > 0 && s < 1) { first = LineLocation.Between; } else { return(Find(a, b, c, d, 2 * epsilon)); } } double tnum = c.Y * bax - c.X * bay + a.X * b.Y - a.Y * b.X; double t = tnum / denom; if ((d3 < 0 && d4 < 0) || (d3 > 0 && d4 > 0)) { if (t < 0) { second = LineLocation.Before; } else if (t > 1) { second = LineLocation.After; } else { return(Find(a, b, c, d, 2 * epsilon)); } } else { if (t > 0 && t < 1) { second = LineLocation.Between; } else { return(Find(a, b, c, d, 2 * epsilon)); } } PointD shared = new PointD(a.X + s * bax, a.Y + s * bay); return(new LineIntersection(shared, first, second, LineRelation.Divergent)); }