//Try to add a voronoi edge. Not all edges have a neighboring triangle, and if it hasnt we cant add a voronoi edge private static void TryAddVoronoiEdgeFromTriangleEdge(HalfEdge e, Vector3 voronoiVertex, List <VoronoiEdge> allEdges) { //Ignore if this edge has no neighboring triangle if (e.oppositeEdge == null) { return; } //Calculate the circumcenter of the neighbor HalfEdge eNeighbor = e.oppositeEdge; Vector3 v1 = eNeighbor.v.position; Vector3 v2 = eNeighbor.nextEdge.v.position; Vector3 v3 = eNeighbor.nextEdge.nextEdge.v.position; //The .XZ() is an extension method that removes the y value of a vector3 so it becomes a vector2 Vector2 center2D = CalculateCircleCenter(new Vector2(v1.x, v1.z), new Vector2(v2.x, v2.z), new Vector2(v3.x, v3.z)); Vector3 voronoiVertexNeighbor = new Vector3(center2D.x, 0f, center2D.y); //Create a new voronoi edge between the voronoi vertices VoronoiEdge edge = new VoronoiEdge(voronoiVertex, voronoiVertexNeighbor, e.prevEdge.v.position); allEdges.Add(edge); }
//Find the position in the list of all cells that includes this site //Returns -1 if no cell is found private static int TryFindCellPos(VoronoiEdge e, List <VoronoiCell> voronoiCells) { for (int i = 0; i < voronoiCells.Count; i++) { if (e.sitePos == voronoiCells[i].sitePos) { return(i); } } return(-1); }
public static List <VoronoiCell> GenerateVoronoiDiagram(List <Vector3> sites) { //First generate the delaunay triangulation List <Triangle> triangles = TriangulateByFlippingEdges(sites); //Generate the voronoi diagram //Step 1. For every delaunay edge, compute a voronoi edge //The voronoi edge is the edge connecting the circumcenters of two neighboring delaunay triangles List <VoronoiEdge> voronoiEdges = new List <VoronoiEdge>(); for (int i = 0; i < triangles.Count; i++) { Triangle t = triangles[i]; //Each triangle consists of these edges HalfEdge e1 = t.halfEdge; HalfEdge e2 = e1.nextEdge; HalfEdge e3 = e2.nextEdge; //Calculate the circumcenter for this triangle Vector3 v1 = e1.v.position; Vector3 v2 = e2.v.position; Vector3 v3 = e3.v.position; //The circumcenter is the center of a circle where the triangles corners is on the circumference of that circle //The .XZ() is an extension method that removes the y value of a vector3 so it becomes a vector2 Vector2 center2D = CalculateCircleCenter(new Vector2(v1.x, v1.z), new Vector2(v2.x, v2.z), new Vector2(v3.x, v3.z)); //The circumcenter is also known as a voronoi vertex, which is a position in the diagram where we are equally //close to the surrounding sites Vector3 voronoiVertex = new Vector3(center2D.x, 0f, center2D.y); TryAddVoronoiEdgeFromTriangleEdge(e1, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e2, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e3, voronoiVertex, voronoiEdges); } //Step 2. Find the voronoi cells where each cell is a list of all edges belonging to a site List <VoronoiCell> voronoiCells = new List <VoronoiCell>(); for (int i = 0; i < voronoiEdges.Count; i++) { VoronoiEdge e = voronoiEdges[i]; //Find the position in the list of all cells that includes this site int cellPos = TryFindCellPos(e, voronoiCells); //No cell was found so we need to create a new cell if (cellPos == -1) { VoronoiCell newCell = new VoronoiCell(e.sitePos); voronoiCells.Add(newCell); newCell.edges.Add(e); } else { voronoiCells[cellPos].edges.Add(e); } } return(voronoiCells); }
private static void ClipDiagram(List <VoronoiCell> cells, float halfWidth) { //Remove the first 4 cells we added in the beginning because they are not needed anymore //cells.RemoveRange(0, 4); for (int i = 0; i < cells.Count; i++) { //We should move around the cell counter-clockwise so make sure all edges are oriented in that way List <VoronoiEdge> cellEdges = cells[i].edges; for (int j = cellEdges.Count - 1; j >= 0; j--) { Vector3 edge_v1 = cellEdges[j].v1; Vector3 edge_v2 = cellEdges[j].v2; //Remove this edge if it is small if ((edge_v1 - edge_v2).sqrMagnitude < 0.01f) { cellEdges.RemoveAt(j); continue; } Vector3 edgeCenter = (edge_v1 + edge_v2) * 0.5f; //Now we can make a line between the cell and the edge Vector2 a = new Vector2(cells[i].sitePos.x, cells[i].sitePos.z); Vector2 b = new Vector2(edgeCenter.x, edgeCenter.z); //The point to the left of this line is coming after the other point if we are moving counter-clockwise if (IsAPointLeftOfVector(a, b, new Vector2(edge_v1.x, edge_v1.z)) < 0f) { //Flip because we want to go from v1 to v2 cellEdges[j].v2 = edge_v1; cellEdges[j].v1 = edge_v2; } } //Connect the edges VoronoiEdge startEdge = cellEdges[0]; cells[i].borderCoordinates.Add(startEdge.v2); Vector3 currentVertex = startEdge.v2; for (int j = 1; j < cellEdges.Count; j++) { //Find the next edge for (int k = 1; k < cellEdges.Count; k++) { Vector3 thisEdgeStart = cellEdges[k].v1; if ((thisEdgeStart - currentVertex).sqrMagnitude < 0.01f) { cells[i].borderCoordinates.Add(cellEdges[k].v2); currentVertex = cellEdges[k].v2; break; } } } } List <Vector3> clipPolygon = new List <Vector3>(); //The positions of the square border Vector3 TL = new Vector3(-halfWidth, 0f, halfWidth); Vector3 TR = new Vector3(halfWidth, 0f, halfWidth); Vector3 BR = new Vector3(halfWidth, 0f, -halfWidth); Vector3 BL = new Vector3(-halfWidth, 0f, -halfWidth); clipPolygon.Add(TL); clipPolygon.Add(BL); clipPolygon.Add(BR); clipPolygon.Add(TR); //Create the clipping planes List <Plane> clippingPlanes = new List <Plane>(); for (int i = 0; i < clipPolygon.Count; i++) { int iPlusOne = ClampListIndex(i + 1, clipPolygon.Count); Vector3 v1 = clipPolygon[i]; Vector3 v2 = clipPolygon[iPlusOne]; //Doesnt have to be center but easier to debug Vector3 planePos = (v1 + v2) * 0.5f; Vector3 planeDir = v2 - v1; //Should point inwards Vector3 planeNormal = new Vector3(-planeDir.z, 0f, planeDir.x).normalized; clippingPlanes.Add(new Plane(planePos, planeNormal)); } for (int i = 0; i < cells.Count; i++) { cells[i].borderCoordinates = ClipPolygon(cells[i].borderCoordinates, clippingPlanes); } }