/// <summary> /// Converts the specified <see cref="PointD"/> to a WPF <see cref="Point"/>.</summary> /// <param name="point"> /// The <see cref="PointD"/> instance to convert.</param> /// <returns> /// A new WPF <see cref="Point"/> instance whose coordinates equal those of the specified /// <paramref name="point"/>.</returns> public static Point ToWpfPoint(this PointD point) { return(new Point(point.X, point.Y)); }
/// <summary> /// Finds the half-edge bounding the <see cref="SubdivisionFace"/> that is nearest to and /// facing the specified coordinates.</summary> /// <param name="q"> /// The <see cref="PointD"/> coordinates to locate.</param> /// <param name="distance"> /// Returns the distance between <paramref name="q"/> and the returned <see /// cref="SubdivisionEdge"/>, if any; otherwise, <see cref="Double.MaxValue"/>.</param> /// <returns><para> /// The <see cref="SubdivisionEdge"/> on any outer or inner boundaries of the <see /// cref="SubdivisionFace"/> with the smallest distance to and facing <paramref name="q"/>. /// </para><para>-or-</para><para> /// A null reference if the <see cref="SubdivisionFace"/> is completely unbounded. /// </para></returns> /// <remarks><para> /// <b>FindNearestEdge</b> traverses the <see cref="OuterEdge"/> boundary and all <see /// cref="InnerEdges"/> boundaries, computing the distance from <paramref name="q"/> to each /// <see cref="SubdivisionEdge"/>. This is an O(n) operation where n is the number of /// half-edges incident on the <see cref="SubdivisionFace"/>. /// </para><para> /// If <paramref name="q"/> is nearest to an edge that belongs to a zero-area protrusion /// into the <see cref="SubdivisionFace"/>, <b>FindNearestEdge</b> returns the twin /// half-edge that faces <paramref name="q"/>, according to its <see /// cref="SubdivisionEdge.Face"/> orientation.</para></remarks> public SubdivisionEdge FindNearestEdge(PointD q, out double distance) { distance = Double.MaxValue; SubdivisionEdge nearestEdge = null; // find smallest distance to any outer cycle edge if (_outerEdge != null) { SubdivisionEdge edge = _outerEdge; do { double d = edge.ToLine().DistanceSquared(q); if (distance > d) { distance = d; if (d == 0) { return(edge); } nearestEdge = edge; } edge = edge._next; } while (edge != _outerEdge); } // find smallest distance to any inner cycle edge if (_innerEdges != null) { for (int i = 0; i < _innerEdges.Count; i++) { SubdivisionEdge innerEdge = _innerEdges[i]; SubdivisionEdge edge = innerEdge; do { double d = edge.ToLine().DistanceSquared(q); if (distance > d) { distance = d; if (d == 0) { return(edge); } nearestEdge = edge; } edge = edge._next; } while (edge != innerEdge); } } if (nearestEdge == null) { return(null); } // check twin in case of zero-area protrusion if (nearestEdge._twin._face == this) { LineLocation location = nearestEdge.ToLine().Locate(q); if (location == LineLocation.Right) { nearestEdge = nearestEdge._twin; } } distance = Math.Sqrt(distance); return(nearestEdge); }
/// <summary> /// Converts the specified <see cref="PointD"/> to a WPF <see cref="Vector"/>.</summary> /// <param name="vector"> /// The <see cref="PointD"/> instance to convert.</param> /// <returns> /// A new WPF <see cref="Vector"/> instance whose coordinates equal those of the specified /// <paramref name="vector"/>.</returns> public static Vector ToWpfVector(this PointD vector) { return(new Vector(vector.X, vector.Y)); }
/// <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)); }
/// <summary> /// Creates the regions of the Voronoi diagram.</summary> /// <remarks> /// <b>CreateRegions</b> stores its output in the <see cref="VoronoiRegions"/> property. /// Please see there for details.</remarks> private void CreateRegions() { /* * 1. Create list of unsorted edges for each region * ================================================ * First we accumulate the raw material for each Voronoi region. * All edges are stored as indices into VoronoiVertices here. */ var listRegions = new LinkedList <PointI> [GeneratorSites.Length]; for (int i = 0; i < listRegions.Length; i++) { listRegions[i] = new LinkedList <PointI>(); } foreach (VoronoiEdge edge in VoronoiEdges) { PointI vertex = new PointI(edge.Vertex1, edge.Vertex2); listRegions[edge.Site1].AddLast(vertex); listRegions[edge.Site2].AddLast(vertex); } /* * 2. Sort and complete list of edges for each region * ================================================== * Sort each edge list so that an edge’s second vertex index equals * the first vertex index of the subsequent edge in the region list. * We may need to swap an edge’s vertex indices to allow this connection. * * Because all Voronoi edges are terminated with a pseudo-vertex where they * intersect the clipping rectangle, a Voronoi region may appear as several * internally connected, but mutually disconnected series of edges. To get * a single connected list, we must then insert pseudo-edges that span either * a border or even a corner of the clipping rectangle. */ for (int i = 0; i < listRegions.Length; i++) { var candidates = listRegions[i]; var listRegion = new LinkedList <PointI>(); // start with first unsorted edge listRegion.AddFirst(candidates.First.Value); candidates.RemoveFirst(); var candidate = candidates.First; bool wasEdgeAdded = false; while (candidate != null) { // save next candidate before removing current one var nextCandidate = candidate.Next; PointI vertex = candidate.Value; for (var node = listRegion.First; node != null; node = node.Next) { Debug.Assert(vertex != node.Value); // all vertices are distinct // invert edges with out-of-order indices if (vertex.X == node.Value.X || vertex.Y == node.Value.Y) { vertex = new PointI(vertex.Y, vertex.X); } // move preceding edge to sorted list if (vertex.Y == node.Value.X) { candidates.Remove(candidate); listRegion.AddBefore(node, vertex); wasEdgeAdded = true; break; } // move succeeding edge to sorted list if (node.Value.Y == vertex.X) { candidates.Remove(candidate); listRegion.AddAfter(node, vertex); wasEdgeAdded = true; break; } } // try next unsorted edge candidate = nextCandidate; if (candidate == null && candidates.Count > 0) { // connection across border or corner required if (!wasEdgeAdded) { ConnectCandidates(candidates, listRegion); } // start over with first candidate node candidate = candidates.First; wasEdgeAdded = false; } } // replace unsorted with sorted list listRegions[i] = listRegion; } /* * 3. Transform index list into polygon for each region * ==================================================== * We now have sorted lists containing all edges of each Voronoi region. * For closed (interior) regions, the last edge connects to the first, * and we store exactly one vertex per edge (we always choose the first). * * For open (exterior) regions, the first edge begins and the last edge ends * with two different pseudo-vertices. We must now close them in the same way * in which we connected separate sublists in step 2. * * That is, we add one or more pseudo-edges that connect the outer vertices * of the list across a border or corner of the clipping rectangle. Unlike * step 2, we may need to extend the connection across two corners. */ _voronoiRegions = new PointD[GeneratorSites.Length][]; for (int i = 0; i < listRegions.Length; i++) { LinkedList <PointI> listRegion = listRegions[i]; int firstIndex = listRegion.First.Value.X; int lastIndex = listRegion.Last.Value.Y; if (firstIndex != lastIndex) { // extend region to last pseudo-vertex listRegion.AddLast(new PointI(lastIndex, Int32.MinValue)); PointD firstVertex = GetVertex(firstIndex); PointD lastVertex = GetVertex(lastIndex); // check if pseudo-vertices span one or two corners of clipping region if (firstVertex.X != lastVertex.X && firstVertex.Y != lastVertex.Y) { CloseCornerRegion(listRegion, firstVertex, lastVertex); } } PointD[] region = new PointD[listRegion.Count]; _voronoiRegions[i] = region; // store coordinates for first vertex of each edge int j = 0; for (var edge = listRegion.First; edge != null; edge = edge.Next, j++) { region[j] = GetVertex(edge.Value.X); } } }
/// <summary> /// Connects the specified candidate edges to the specified Voronoi region by inserting /// edges that span a border or corner of the <see cref="ClippingBounds"/>.</summary> /// <param name="candidates"> /// A <see cref="LinkedList{PointI}"/> containing the candidate edges that must be connected /// to the specified <paramref name="region"/>.</param> /// <param name="region"> /// A <see cref="LinkedList{PointI}"/> containing the sorted and connected edges of the /// Voronoi region. The first and last vertex must touch the <see cref="ClippingBounds"/>. /// </param> /// <remarks> /// <b>ConnectCandidates</b> adds one or two edges at the beginning of the specified /// <paramref name="candidates"/> list. If two edges are added, both will contain one /// pseudo-vertex represented by a <see cref="CornerIndex"/> value.</remarks> private void ConnectCandidates(LinkedList <PointI> candidates, LinkedList <PointI> region) { int firstIndex = region.First.Value.X; int lastIndex = region.Last.Value.Y; PointD firstVertex = GetVertex(firstIndex); PointD lastVertex = GetVertex(lastIndex); Debug.Assert(IsAtClippingBounds(firstVertex)); Debug.Assert(IsAtClippingBounds(lastVertex)); // outer vertex indices of connecting edge(s) int connect1 = -1, connect2 = -1; foreach (PointI candidate in candidates) { connect1 = candidate.X; PointD connectVertex = GetVertex(connect1); if (MeetAtClippingBounds(connectVertex, firstVertex)) { connect2 = firstIndex; break; } if (MeetAtClippingBounds(connectVertex, lastVertex)) { connect2 = lastIndex; break; } connect1 = candidate.Y; connectVertex = GetVertex(connect1); if (MeetAtClippingBounds(connectVertex, firstVertex)) { connect2 = firstIndex; break; } if (MeetAtClippingBounds(connectVertex, lastVertex)) { connect2 = lastIndex; break; } } if (connect2 >= 0) { // add connecting edge on same clipping border candidates.AddFirst(new PointI(connect1, connect2)); return; } connect1 = -1; connect2 = -1; CornerIndex corner = CornerIndex.None; foreach (PointI candidate in candidates) { connect1 = candidate.X; PointD connectVertex = GetVertex(connect1); corner = GetCornerIndex(connectVertex, firstVertex); if (corner != CornerIndex.None) { connect2 = firstIndex; break; } corner = GetCornerIndex(connectVertex, lastVertex); if (corner != CornerIndex.None) { connect2 = lastIndex; break; } connect1 = candidate.Y; connectVertex = GetVertex(connect1); corner = GetCornerIndex(connectVertex, firstVertex); if (corner != CornerIndex.None) { connect2 = firstIndex; break; } corner = GetCornerIndex(connectVertex, lastVertex); if (corner != CornerIndex.None) { connect2 = lastIndex; break; } } // add connecting edges across clipping corner Debug.Assert(connect2 >= 0); candidates.AddFirst(new PointI((int)corner, connect2)); candidates.AddFirst(new PointI(connect1, (int)corner)); }
/// <summary> /// Determines whether the specified two <see cref="PointD"/> coordinates lie on the same /// border of the <see cref="ClippingBounds"/>.</summary> /// <param name="p"> /// The first <see cref="PointD"/> to examine.</param> /// <param name="q"> /// The second <see cref="PointD"/> to examine.</param> /// <returns> /// <c>true</c> if the <see cref="PointD.X"/> components of <paramref name="p"/> and /// <paramref name="q"/> both equal the <see cref="RectD.Left"/> or <see /// cref="RectD.Right"/> border of the <see cref="ClippingBounds"/>, or if their <see /// cref="PointD.Y"/> components both equal the <see cref="RectD.Top"/> or <see /// cref="RectD.Bottom"/> border; otherwise, <c>false</c>.</returns> private bool MeetAtClippingBounds(PointD p, PointD q) { return((p.X == q.X && (p.X == ClippingBounds.Left || p.X == ClippingBounds.Right)) || (p.Y == q.Y && (p.Y == ClippingBounds.Top || p.Y == ClippingBounds.Bottom))); }