/// <summary> /// Return an edge that is the bisection between two sites /// </summary> /// <param name="siteA">The first site</param> /// <param name="siteB">The second site</param> /// <returns>The new edge</returns> public static VoronoiDiagramEdge <T> Bisect(VoronoiDiagramSite <T> siteA, VoronoiDiagramSite <T> siteB) { float dx, dy; var newEdge = new VoronoiDiagramEdge <T> { LeftSite = siteA, RightSite = siteB, LeftEndPoint = null, RightEndPoint = null }; dx = siteB.Coordinate.x - siteA.Coordinate.x; dy = siteB.Coordinate.y - siteA.Coordinate.y; newEdge.C = siteA.Coordinate.x * dx + siteA.Coordinate.y * dy + (dx * dx + dy * dy) * 0.5f; if (Mathf.Abs(dx) > Mathf.Abs(dy)) { newEdge.A = 1f; newEdge.B = dy / dx; newEdge.C /= dx; } else { newEdge.B = 1f; newEdge.A = dx / dy; newEdge.C /= dy; } siteA.Edges.Add(newEdge); siteB.Edges.Add(newEdge); return(newEdge); }
/// <summary> /// Creates a new half edge /// </summary> /// <param name="edge">The edge of the new half edge</param> /// <param name="edgeType">The edge type that this half edge represents (left or right). The only half edges that should be "None" are the buckets in the priority queue.</param> public VoronoiDiagramHalfEdge(VoronoiDiagramEdge <T> edge, VoronoiDiagramEdgeType edgeType) { Edge = edge; EdgeType = edgeType; Vertex = null; StarY = 0f; EdgeListLeft = null; EdgeListRight = null; NextInPriorityQueue = null; }
/// <summary> /// Runs Fortune's Algorithm to generate sites with edges for the diagram /// </summary> /// <param name="relaxationCycles">Number of relaxation cycles to run</param> public void GenerateSites(int relaxationCycles) { if (_originalSites.Count == 0) { Debug.LogError("No points added to the diagram. Sites cannot be generated"); return; } _sites.Clear(); foreach (VoronoiDiagramSite <T> site in _originalSites) { _sites.Add(new VoronoiDiagramSite <T>(_sites.Count, site)); } SortSitesAndSetValues(); // Cycles related to Lloyd's algorithm for (int cycles = 0; cycles < relaxationCycles; cycles++) { // Fortune's Algorithm int numGeneratedEdges = 0; int numGeneratedVertices = 0; _currentSiteIndex = 0; var priorityQueue = new VoronoiDiagramPriorityQueue <T>(_sites.Count, _minValues, _deltaValues); var edgeList = new VoronoiDiagramEdgeList <T>(_sites.Count, _minValues, _deltaValues); Vector2 currentIntersectionStar = Vector2.zero; VoronoiDiagramSite <T> currentSite; var generatedEdges = new List <VoronoiDiagramEdge <T> >(); bool done = false; _bottomMostSite = GetNextSite(); currentSite = GetNextSite(); while (!done) { if (!priorityQueue.IsEmpty()) { currentIntersectionStar = priorityQueue.GetMinimumBucketFirstPoint(); } VoronoiDiagramSite <T> bottomSite; VoronoiDiagramHalfEdge <T> bisector; VoronoiDiagramHalfEdge <T> rightBound; VoronoiDiagramHalfEdge <T> leftBound; VoronoiDiagramVertex <T> vertex; VoronoiDiagramEdge <T> edge; if ( currentSite != null && ( priorityQueue.IsEmpty() || currentSite.Coordinate.y < currentIntersectionStar.y || ( currentSite.Coordinate.y.IsAlmostEqualTo(currentIntersectionStar.y) && currentSite.Coordinate.x < currentIntersectionStar.x ) ) ) { // Current processed site is the smallest leftBound = edgeList.GetLeftBoundFrom(currentSite.Coordinate); rightBound = leftBound.EdgeListRight; bottomSite = GetRightRegion(leftBound); edge = VoronoiDiagramEdge <T> .Bisect(bottomSite, currentSite); edge.Index = numGeneratedEdges; numGeneratedEdges++; generatedEdges.Add(edge); bisector = new VoronoiDiagramHalfEdge <T>(edge, VoronoiDiagramEdgeType.Left); edgeList.Insert(leftBound, bisector); vertex = VoronoiDiagramVertex <T> .Intersect(leftBound, bisector); if (vertex != null) { priorityQueue.Delete(leftBound); leftBound.Vertex = vertex; leftBound.StarY = vertex.Coordinate.y + currentSite.GetDistanceFrom(vertex); priorityQueue.Insert(leftBound); } leftBound = bisector; bisector = new VoronoiDiagramHalfEdge <T>(edge, VoronoiDiagramEdgeType.Right); edgeList.Insert(leftBound, bisector); vertex = VoronoiDiagramVertex <T> .Intersect(bisector, rightBound); if (vertex != null) { bisector.Vertex = vertex; bisector.StarY = vertex.Coordinate.y + currentSite.GetDistanceFrom(vertex); priorityQueue.Insert(bisector); } currentSite = GetNextSite(); } else if (priorityQueue.IsEmpty() == false) { // Current intersection is the smallest leftBound = priorityQueue.RemoveAndReturnMinimum(); VoronoiDiagramHalfEdge <T> leftLeftBound = leftBound.EdgeListLeft; rightBound = leftBound.EdgeListRight; VoronoiDiagramHalfEdge <T> rightRightBound = rightBound.EdgeListRight; bottomSite = GetLeftRegion(leftBound); VoronoiDiagramSite <T> topSite = GetRightRegion(rightBound); // These three sites define a Delaunay triangle // Bottom, Top, EdgeList.GetRightRegion(rightBound); // Debug.Log(string.Format("Delaunay triagnle: ({0}, {1}), ({2}, {3}), ({4}, {5})"), // bottomSite.Coordinate.x, bottomSite.Coordinate.y, // topSite.Coordinate.x, topSite.Coordinate.y, // edgeList.GetRightRegion(leftBound).Coordinate.x, // edgeList.GetRightRegion(leftBound).Coordinate.y); var v = leftBound.Vertex; v.Index = numGeneratedVertices; numGeneratedVertices++; leftBound.Edge.SetEndpoint(v, leftBound.EdgeType); rightBound.Edge.SetEndpoint(v, rightBound.EdgeType); edgeList.Delete(leftBound); priorityQueue.Delete(rightBound); edgeList.Delete(rightBound); var edgeType = VoronoiDiagramEdgeType.Left; if (bottomSite.Coordinate.y > topSite.Coordinate.y) { var tempSite = bottomSite; bottomSite = topSite; topSite = tempSite; edgeType = VoronoiDiagramEdgeType.Right; } edge = VoronoiDiagramEdge <T> .Bisect(bottomSite, topSite); edge.Index = numGeneratedEdges; numGeneratedEdges++; generatedEdges.Add(edge); bisector = new VoronoiDiagramHalfEdge <T>(edge, edgeType); edgeList.Insert(leftLeftBound, bisector); edge.SetEndpoint(v, edgeType == VoronoiDiagramEdgeType.Left ? VoronoiDiagramEdgeType.Right : VoronoiDiagramEdgeType.Left); vertex = VoronoiDiagramVertex <T> .Intersect(leftLeftBound, bisector); if (vertex != null) { priorityQueue.Delete(leftLeftBound); leftLeftBound.Vertex = vertex; leftLeftBound.StarY = vertex.Coordinate.y + bottomSite.GetDistanceFrom(vertex); priorityQueue.Insert(leftLeftBound); } vertex = VoronoiDiagramVertex <T> .Intersect(bisector, rightRightBound); if (vertex != null) { bisector.Vertex = vertex; bisector.StarY = vertex.Coordinate.y + bottomSite.GetDistanceFrom(vertex); priorityQueue.Insert(bisector); } } else { done = true; } } GeneratedSites.Clear(); // Bound the edges of the diagram foreach (VoronoiDiagramEdge <T> currentGeneratedEdge in generatedEdges) { currentGeneratedEdge.GenerateClippedEndPoints(Bounds); } foreach (VoronoiDiagramSite <T> site in _sites) { try { site.GenerateCentroid(Bounds); } catch (Exception) { Debug.Log("Coordinate"); Debug.Log(site.Coordinate); Debug.Log("End points:"); foreach (var edge in site.Edges) { Debug.Log(edge.LeftClippedEndPoint + " , " + edge.RightClippedEndPoint); } throw; } } foreach (VoronoiDiagramSite <T> site in _sites) { var generatedSite = new VoronoiDiagramGeneratedSite <T>(site.Index, site.Coordinate, site.Centroid, new T(), site.IsCorner, site.IsEdge); generatedSite.Vertices.AddRange(site.Vertices); generatedSite.SiteData = site.SiteData; foreach (VoronoiDiagramEdge <T> siteEdge in site.Edges) { // Only add edges that are visible // Don't need to check the Right because they will both be float.MinValue if (siteEdge.LeftClippedEndPoint == new Vector2(float.MinValue, float.MinValue)) { continue; } generatedSite.Edges.Add(new VoronoiDiagramGeneratedEdge(siteEdge.Index, siteEdge.LeftClippedEndPoint, siteEdge.RightClippedEndPoint)); if (siteEdge.LeftSite != null && !generatedSite.NeighborSites.Contains(siteEdge.LeftSite.Index)) { generatedSite.NeighborSites.Add(siteEdge.LeftSite.Index); } if (siteEdge.RightSite != null && !generatedSite.NeighborSites.Contains(siteEdge.RightSite.Index)) { generatedSite.NeighborSites.Add(siteEdge.RightSite.Index); } } GeneratedSites.Add(generatedSite.Index, generatedSite); // Finished with the edges, remove the references so they can be removed at the end of the method site.Edges.Clear(); } // Clean up _bottomMostSite = null; _sites.Clear(); // Lloyd's Algorithm foreach (KeyValuePair <int, VoronoiDiagramGeneratedSite <T> > generatedSite in GeneratedSites) { var centroidPoint = new Vector2( Mathf.Clamp(generatedSite.Value.Centroid.x, 0, Bounds.width), Mathf.Clamp(generatedSite.Value.Centroid.y, 0, Bounds.height)); var newSite = new VoronoiDiagramSite <T>(new Vector2(centroidPoint.x, centroidPoint.y), generatedSite.Value.SiteData); if (!_sites.Any(item => item.Coordinate.x.IsAlmostEqualTo(newSite.Coordinate.x) && item.Coordinate.y.IsAlmostEqualTo(newSite.Coordinate.y))) { _sites.Add(new VoronoiDiagramSite <T>(_sites.Count, newSite)); } } SortSitesAndSetValues(); } }