/// <summary> /// Finds the vertical border of the <see cref="ClippingBounds"/> towards which the Voronoi /// region containing the specified horizontal border coordinates is open.</summary> /// <param name="p"> /// The first <see cref="PointD"/> to examine.</param> /// <param name="q"> /// The second <see cref="PointD"/> to examine.</param> /// <returns> /// The <see cref="RectD.Left"/> or <see cref="RectD.Right"/> border of the <see /// cref="ClippingBounds"/>, depending on the specified <paramref name="p"/> and <paramref /// name="q"/>.</returns> /// <remarks> /// The specified <paramref name="p"/> and <paramref name="q"/> must lie on opposite /// horizontal borders of the <see cref="ClippingBounds"/>, and their Voronoi region must /// open to one of the vertical borders, including both adjacent corners.</remarks> private double FindVerticalBorder(PointD p, PointD q) { // line from top to bottom of clipping bounds LineD line = (p.Y < q.Y) ? new LineD(p, q) : new LineD(q, p); Debug.Assert(line.Start.Y == ClippingBounds.Top); Debug.Assert(line.End.Y == ClippingBounds.Bottom); // check for vertex on either side of line // (LineLocation assumes y-coordinates increase upward!) foreach (PointD vertex in VoronoiVertices) { switch (line.Locate(vertex)) { case LineLocation.Left: return(ClippingBounds.Right); case LineLocation.Right: return(ClippingBounds.Left); } } Debug.Fail("Cannot identify open side of Voronoi region."); return(ClippingBounds.Left); }
/// <summary> /// Splits the specified line segments on the specified intersection points.</summary> /// <param name="lines"> /// An <see cref="Array"/> containing the <see cref="LineD"/> instances to split.</param> /// <param name="crossings"> /// An <see cref="Array"/> of <see cref="MultiLinePoint"/> instances containing all points /// of intersection between the <paramref name="lines"/>.</param> /// <returns> /// An <see cref="Array"/> containing the <see cref="LineD"/> instances resulting from /// splitting all <paramref name="lines"/> on the matching <paramref name="crossings"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="lines"/> or <paramref name="crossings"/> is a null reference. /// </exception> /// <exception cref="IndexOutOfRangeException"> /// <paramref name="crossings"/> contains one or more <see cref="MultiLinePoint.Lines"/> /// indices that are equal to or greater than the number of <paramref name="lines"/>. /// </exception> /// <remarks><para> /// <b>Split</b> returns a collection of line segments that are guaranteed not to intersect, /// except at their <see cref="LineD.Start"/> or <see cref="LineD.End"/> points. The /// specified <paramref name="crossings"/> are usually the result of <see cref="Find"/> or /// <see cref="FindSimple"/> for the specified <paramref name="lines"/>. /// </para><para> /// <b>Split</b> sets the <see cref="LineD.Start"/> or <see cref="LineD.End"/> point of any /// line segment that participates in a <see cref="LineLocation.Start"/> or <see /// cref="LineLocation.End"/> intersection to the <see cref="MultiLinePoint.Shared"/> /// coordinates of that intersection, so as to preserve coordinate identities that were /// established by a positive comparison epsilon.</para></remarks> public static LineD[] Split(LineD[] lines, MultiLinePoint[] crossings) { if (lines == null) { ThrowHelper.ThrowArgumentNullException("lines"); } if (crossings == null) { ThrowHelper.ThrowArgumentNullException("crossings"); } // list of split segments, or null to use original line int count = lines.Length; List <PointD>[] segmentPoints = new List <PointD> [count]; // process all epsilon-matched intersection points foreach (MultiLinePoint crossing in crossings) { for (int k = 0; k < crossing.Lines.Length; k++) { int i = crossing.Lines[k]; List <PointD> points = segmentPoints[i]; // initialize split segment list with start & end points if (points == null) { points = new List <PointD>(4); points.Add(lines[i].Start); points.Add(lines[i].End); segmentPoints[i] = points; } switch (crossing.Locations[k]) { case LineLocation.Start: // replace start point with epsilon-matched intersection points[0] = crossing.Shared; break; case LineLocation.End: // replace end point with epsilon-matched intersection points[1] = crossing.Shared; break; case LineLocation.Between: // add intersection point that defines new split segment points.Add(crossing.Shared); ++count; break; } } } /* * Comparison function to sort split segment point list. * * We cannot use lexicographic or other single-coordinate comparisons because * epsilon matching might cause coordinate aberrations in the wrong direction. * So we compare the squared distances of both points from the start point. */ PointD start = PointD.Empty; Comparison <PointD> comparison = (a, b) => { double ax = a.X - start.X, ay = a.Y - start.Y; double bx = b.X - start.X, by = b.Y - start.Y; double d = (ax * ax + ay * ay) - (bx * bx + by * by); if (d < 0) { return(-1); } if (d > 0) { return(+1); } return(0); }; LineD[] segments = new LineD[count]; int index = 0; for (int i = 0; i < lines.Length; i++) { List <PointD> points = segmentPoints[i]; if (points == null) { // no intersections, store original line segments[index++] = lines[i]; } else { Debug.Assert(points.Count > 2); // sort points by distance from start point start = points[0]; points.Sort(comparison); // convert sorted points to split line segments for (int j = 0; j < points.Count - 1; j++) { segments[index++] = new LineD(points[j], points[j + 1]); } } } Debug.Assert(index == count); return(segments); }