// Look for a new circle event for the arc at ArcIndex private void checkCircleEvent(List <circleEvent> circleEvents, int arcIndex, double scanX) { if (arcIndex == 0 || arcIndex == Arcs.Count - 1) { return; } arc arc = Arcs[arcIndex]; double maxX; PointD center; if (getCircle(Arcs[arcIndex - 1].Site, arc.Site, Arcs[arcIndex + 1].Site, out center, out maxX) /* && MaxX >= ScanX*/) { // Add the new event in the right place using binary search int low = 0; int high = circleEvents.Count; while (low < high) { int middle = (low + high) / 2; circleEvent evt = circleEvents[middle]; if (evt.X < maxX || (evt.X == maxX && evt.Center.Y < center.Y)) { low = middle + 1; } else { high = middle; } } circleEvents.Insert(low, new circleEvent(maxX, center, arc)); } }
public data(PointD[] sites, float width, float height, VoronoiDiagramFlags flags) { foreach (PointD p in sites) { if (p.X > 0 && p.Y > 0 && p.X < width && p.Y < height) { SiteEvents.Add(new siteEvent(p)); } else if ((flags & VoronoiDiagramFlags.RemoveOffboundsSites) == 0) { throw new Exception("The input contains a point outside the bounds or on the perimeter (coordinates " + p + "). This case is not handled by this algorithm. Use the RT.Util.VoronoiDiagramFlags.REMOVE_OFFBOUNDS_SITES " + "flag to automatically remove such off-bounds input points."); } } SiteEvents.Sort(); // Make sure there are no two equal points in the input for (int i = 1; i < SiteEvents.Count; i++) { while (i < SiteEvents.Count && SiteEvents[i - 1].Position == SiteEvents[i].Position) { if ((flags & VoronoiDiagramFlags.RemoveDuplicates) == VoronoiDiagramFlags.RemoveDuplicates) { SiteEvents.RemoveAt(i); } else { throw new Exception("The input contains two points at the same coordinates " + SiteEvents[i].Position + ". Voronoi diagrams are undefined for such a situation. " + "Use the RT.Util.VoronoiDiagramFlags.REMOVE_DUPLICATES flag to automatically remove such duplicate input points."); } } } // Main loop while (SiteEvents.Count > 0 || CircleEvents.Count > 0) { if (CircleEvents.Count > 0 && (SiteEvents.Count == 0 || CircleEvents[0].X <= SiteEvents[0].Position.X)) { // Process a circle event circleEvent evt = CircleEvents[0]; CircleEvents.RemoveAt(0); int arcIndex = Arcs.IndexOf(evt.Arc); if (arcIndex == -1) { continue; } // The two edges left and right of the disappearing arc end here if (Arcs[arcIndex - 1].Edge != null) { Arcs[arcIndex - 1].Edge.SetEndPoint(evt.Center); } if (evt.Arc.Edge != null) { evt.Arc.Edge.SetEndPoint(evt.Center); } // Remove the arc from the beachline Arcs.RemoveAt(arcIndex); // ArcIndex now points to the arc after the one that disappeared // Start a new edge at the point where the other two edges ended Arcs[arcIndex - 1].Edge = new edge(Arcs[arcIndex - 1].Site, Arcs[arcIndex].Site); Arcs[arcIndex - 1].Edge.SetEndPoint(evt.Center); Edges.Add(Arcs[arcIndex - 1].Edge); // Recheck circle events on either side of the disappearing arc if (arcIndex > 0) { checkCircleEvent(CircleEvents, arcIndex - 1, evt.X); } if (arcIndex < Arcs.Count) { checkCircleEvent(CircleEvents, arcIndex, evt.X); } } else { // Process a site event siteEvent evt = SiteEvents[0]; SiteEvents.RemoveAt(0); if (Arcs.Count == 0) { Arcs.Add(new arc(evt.Position)); continue; } // Find the current arc(s) at height e.Position.y (if there are any) bool arcFound = false; for (int i = 0; i < Arcs.Count; i++) { PointD intersect; if (doesIntersect(evt.Position, i, out intersect)) { // New parabola intersects Arc - duplicate Arc Arcs.Insert(i + 1, new arc(Arcs[i].Site)); Arcs[i + 1].Edge = Arcs[i].Edge; // Add a new Arc for Event.Position in the right place Arcs.Insert(i + 1, new arc(evt.Position)); // Add new half-edges connected to Arc's endpoints Arcs[i].Edge = Arcs[i + 1].Edge = new edge(Arcs[i + 1].Site, Arcs[i + 2].Site); Edges.Add(Arcs[i].Edge); // Check for new circle events around the new arc: checkCircleEvent(CircleEvents, i, evt.Position.X); checkCircleEvent(CircleEvents, i + 2, evt.Position.X); arcFound = true; break; } } if (arcFound) { continue; } // Special case: If Event.Position never intersects an arc, append it to the list. // This only happens if there is more than one site event with the lowest X co-ordinate. arc lastArc = Arcs[Arcs.Count - 1]; arc newArc = new arc(evt.Position); lastArc.Edge = new edge(lastArc.Site, newArc.Site); Edges.Add(lastArc.Edge); lastArc.Edge.SetEndPoint(new PointD(0, (newArc.Site.Y + lastArc.Site.Y) / 2)); Arcs.Add(newArc); } } // Advance the sweep line so no parabolas can cross the bounding box double var = 2 * width + height; // Extend each remaining edge to the new parabola intersections for (int i = 0; i < Arcs.Count - 1; i++) { if (Arcs[i].Edge != null) { Arcs[i].Edge.SetEndPoint(getIntersection(Arcs[i].Site, Arcs[i + 1].Site, 2 * var)); } } // Clip all the edges with the bounding rectangle and remove edges that are entirely outside var newEdges = new List <edge>(); var boundingEdges = new[] { new EdgeD(0, 0, width, 0), new EdgeD(width, 0, width, height), new EdgeD(width, height, 0, height), new EdgeD(0, height, 0, 0) }; foreach (edge e in Edges) { if ((e.Start.Value.X < 0 || e.Start.Value.X > width || e.Start.Value.Y < 0 || e.Start.Value.Y > width) && (e.End.Value.X < 0 || e.End.Value.X > width || e.End.Value.Y < 0 || e.End.Value.Y > width) && !boundingEdges.Any(be => be.IntersectsWith(new EdgeD(e.Start.Value, e.End.Value)))) { continue; } if (e.Start.Value.X < 0) { e.Start = new PointD(0, e.End.Value.X / (e.End.Value.X - e.Start.Value.X) * (e.Start.Value.Y - e.End.Value.Y) + e.End.Value.Y); } if (e.Start.Value.Y < 0) { e.Start = new PointD(e.End.Value.Y / (e.End.Value.Y - e.Start.Value.Y) * (e.Start.Value.X - e.End.Value.X) + e.End.Value.X, 0); } if (e.End.Value.X < 0) { e.End = new PointD(0, e.Start.Value.X / (e.Start.Value.X - e.End.Value.X) * (e.End.Value.Y - e.Start.Value.Y) + e.Start.Value.Y); } if (e.End.Value.Y < 0) { e.End = new PointD(e.Start.Value.Y / (e.Start.Value.Y - e.End.Value.Y) * (e.End.Value.X - e.Start.Value.X) + e.Start.Value.X, 0); } if (e.Start.Value.X > width) { e.Start = new PointD(width, (width - e.Start.Value.X) / (e.End.Value.X - e.Start.Value.X) * (e.End.Value.Y - e.Start.Value.Y) + e.Start.Value.Y); } if (e.Start.Value.Y > height) { e.Start = new PointD((height - e.Start.Value.Y) / (e.End.Value.Y - e.Start.Value.Y) * (e.End.Value.X - e.Start.Value.X) + e.Start.Value.X, height); } if (e.End.Value.X > width) { e.End = new PointD(width, (width - e.End.Value.X) / (e.Start.Value.X - e.End.Value.X) * (e.Start.Value.Y - e.End.Value.Y) + e.End.Value.Y); } if (e.End.Value.Y > height) { e.End = new PointD((height - e.End.Value.Y) / (e.Start.Value.Y - e.End.Value.Y) * (e.Start.Value.X - e.End.Value.X) + e.End.Value.X, height); } newEdges.Add(e); } Edges = newEdges; // Generate polygons from the edges foreach (edge e in Edges) { if (!Polygons.ContainsKey(e.SiteA)) { Polygons.Add(e.SiteA, new polygon(e.SiteA)); } Polygons[e.SiteA].AddEdge(e); if (!Polygons.ContainsKey(e.SiteB)) { Polygons.Add(e.SiteB, new polygon(e.SiteB)); } Polygons[e.SiteB].AddEdge(e); } }