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); } }
/// <summary> /// Generates a Voronoi diagram from a set of input points.</summary> /// <param name="sites"> /// Input points (sites) to generate diagram from.</param> /// <param name="size"> /// Size of the viewport. The origin of the viewport is assumed to be at (0, 0).</param> /// <param name="flags"> /// Set of <see cref="VoronoiDiagramFlags"/> values that specifies additional options.</param> /// <returns> /// A list of line segments describing the Voronoi diagram.</returns> public static VoronoiDiagram GenerateVoronoiDiagram(PointD[] sites, SizeF size, VoronoiDiagramFlags flags = 0) { return(new data(sites, size.Width, size.Height, flags).Apply(data => new VoronoiDiagram { Edges = data.Edges.Select(edge => new EdgeD(edge.Start.Value, edge.End.Value)).ToList(), Polygons = data.Polygons .Select(kvp => Ut.KeyValuePair(kvp.Key, kvp.Value.ToPolygonD(flags.HasFlag(VoronoiDiagramFlags.IncludeEdgePolygons), size.Width, size.Height))) .Where(kvp => kvp.Value != null) .ToDictionary() })); }