/// <summary> /// Returns the middle point and angle of a border to an adjacent region. The angle always points to the direction of this region. /// </summary> public Tuple <Vector2, float> GetBorderCenterPositionTo(Region otherRegion) { if (!AdjacentRegions.Contains(otherRegion)) { throw new Exception("Other region is not adjacent to this region"); } // Find all borders between the two regions List <Border> borders = Borders.Where(x => x.Regions.Contains(otherRegion)).ToList(); // Split borders into clusters and take longest cluster List <List <Border> > clusters = PolygonMapFunctions.FindBorderClusters(borders); List <Border> longestCluster = clusters.OrderByDescending(x => x.Sum(y => y.Length)).First(); // Find center of longest cluster Tuple <Vector2, float> center = PolygonMapFunctions.FindBorderCenter(longestCluster); // Swap angle if the border was reversed Vector2 testPoint = center.Item1 + new Vector2(Mathf.Sin(Mathf.Deg2Rad * center.Item2) * 0.01f, Mathf.Cos(Mathf.Deg2Rad * center.Item2) * 0.01f); if (!GeometryFunctions.IsPointInPolygon4(Polygon.Nodes.Select(x => x.Vertex).ToList(), testPoint)) { center = new Tuple <Vector2, float>(center.Item1, center.Item2 + 180); } return(center); }
public static River CreateRiverObject(GraphPath riverPath, PolygonMapGenerator PMG) { //Debug.Log("Creating mesh for river with " + riverPath.Nodes.Count + " points"); // Calculate vertices of river polygon List <Vector2> polygonVerticesHalf1 = new List <Vector2>(); List <Vector2> polygonVerticesHalf2 = new List <Vector2>(); for (int i = 1; i < riverPath.Nodes.Count - 1; i++) { Vector2 startPoint = riverPath.Nodes[i - 1].Vertex; Vector2 thisPoint = riverPath.Nodes[i].Vertex; Vector2 nextPoint = riverPath.Nodes[i + 1].Vertex; float startWidth = riverPath.Nodes[i - 1].RiverWidth; float endWidth = riverPath.Nodes[i].RiverWidth; //Debug.Log("River point " + i + ": startWidth = " + startWidth + ", endWidth = " + endWidth); if (i == 1) // Add two starting points { Vector2 rotatedVector = GeometryFunctions.RotateVector((thisPoint - startPoint).normalized * startWidth, 90); polygonVerticesHalf1.Add(startPoint + rotatedVector); polygonVerticesHalf2.Add(startPoint - rotatedVector); } polygonVerticesHalf1.Add(GeometryFunctions.GetOffsetIntersection(startPoint, thisPoint, nextPoint, startWidth, endWidth, true)); polygonVerticesHalf2.Add(GeometryFunctions.GetOffsetIntersection(startPoint, thisPoint, nextPoint, startWidth, endWidth, false)); if (i == riverPath.Nodes.Count - 2) // Add two ending points (calculate river delta by taking intersecion between river and shoreline { GraphNode lastNode = riverPath.Nodes.Last(); List <GraphConnection> shoreDelta = lastNode.Connections.Where(x => x.Type == BorderType.Shore).ToList(); List <GraphNode> riverDeltaPoints = new List <GraphNode>(); foreach (GraphConnection delta in shoreDelta) { if (delta.StartNode == lastNode) { riverDeltaPoints.Add(delta.EndNode); } else { riverDeltaPoints.Add(delta.StartNode); } } Vector2 endPoint1, endPoint2; // BUG: this method doesn't work 100% GraphPolygon firstPolygon = riverPath.Nodes[i].Polygons.FirstOrDefault(x => GeometryFunctions.IsPointInPolygon4(x.Nodes.Select(x => x.Vertex).ToList(), polygonVerticesHalf1.Last())); if (firstPolygon == null) { throw new Exception("Couldn't find direction of river delta. is river too short? length = " + riverPath.Nodes.Count); } bool addDeltaMidPoint = true; if (riverDeltaPoints[0].Polygons.Contains(firstPolygon)) { endPoint1 = GeometryFunctions.GetOffsetIntersection(thisPoint, nextPoint, riverDeltaPoints[0].Vertex, endWidth, 0f, true); endPoint2 = GeometryFunctions.GetOffsetIntersection(thisPoint, nextPoint, riverDeltaPoints[1].Vertex, endWidth, 0f, false); if (!GeometryFunctions.IsPointOnLineSegment(endPoint1, riverDeltaPoints[0].Vertex, lastNode.Vertex)) { endPoint1 = lastNode.Vertex; addDeltaMidPoint = false; } if (!GeometryFunctions.IsPointOnLineSegment(endPoint2, riverDeltaPoints[1].Vertex, lastNode.Vertex)) { endPoint2 = lastNode.Vertex; addDeltaMidPoint = false; } } else { endPoint1 = GeometryFunctions.GetOffsetIntersection(thisPoint, nextPoint, riverDeltaPoints[1].Vertex, endWidth, 0f, true); endPoint2 = GeometryFunctions.GetOffsetIntersection(thisPoint, nextPoint, riverDeltaPoints[0].Vertex, endWidth, 0f, false); if (!GeometryFunctions.IsPointOnLineSegment(endPoint1, riverDeltaPoints[1].Vertex, lastNode.Vertex)) { endPoint1 = lastNode.Vertex; addDeltaMidPoint = false; } if (!GeometryFunctions.IsPointOnLineSegment(endPoint2, riverDeltaPoints[0].Vertex, lastNode.Vertex)) { endPoint2 = lastNode.Vertex; addDeltaMidPoint = false; } } polygonVerticesHalf1.Add(endPoint1); if (addDeltaMidPoint) { polygonVerticesHalf1.Add(lastNode.Vertex); } polygonVerticesHalf2.Add(endPoint2); } } polygonVerticesHalf2.Reverse(); List <Vector2> polygonVertices = polygonVerticesHalf1; polygonVertices.AddRange(polygonVerticesHalf2); List <Vector2> polygonVerticesList = polygonVertices.ToList(); if (GeometryFunctions.IsClockwise(polygonVerticesList)) { polygonVerticesList.Reverse(); } // Create object GameObject riverObject = MeshGenerator.GeneratePolygon(polygonVerticesList, PMG, layer: PolygonMapGenerator.LAYER_RIVER); River river = riverObject.AddComponent <River>(); river.Init(riverPath.Nodes.Select(x => x.BorderPoint).ToList(), riverPath.Connections.Select(x => x.Border).ToList(), riverPath.Polygons.Select(x => x.Region).ToList()); riverObject.GetComponent <MeshRenderer>().material.color = Color.red; riverObject.name = "River"; return(river); }
public static GameObject LabelPolygon(List <List <GraphNode> > polygonBorders, string label, Color color) { // 1. Compute voronoi of given points with "VoronoiLib" List <FortuneSite> points = new List <FortuneSite>(); List <GraphNode> nodes = new List <GraphNode>(); foreach (List <GraphNode> polygonBorder in polygonBorders) { foreach (GraphNode n in polygonBorder) { if (!nodes.Contains(n)) { nodes.Add(n); } } } foreach (GraphNode n in nodes) { points.Add(new FortuneSite(n.Vertex.x, n.Vertex.y)); } float margin = 0.2f; float minX = nodes.Min(x => x.Vertex.x) - margin; float maxX = nodes.Max(x => x.Vertex.x) + margin; float minY = nodes.Min(x => x.Vertex.y) - margin; float maxY = nodes.Max(x => x.Vertex.y) + margin; LinkedList <VEdge> voronoi = FortunesAlgorithm.Run(points, minX, minY, maxX, maxY); // 2. Remove all edges that have either start or end outside of polygon List <Vector2> vertices = nodes.Select(x => x.Vertex).ToList(); List <VEdge> edgesToRemove = new List <VEdge>(); foreach (VEdge edge in voronoi) { if (!GeometryFunctions.IsPointInPolygon4(vertices, new Vector2((float)edge.Start.X, (float)edge.Start.Y)) || !GeometryFunctions.IsPointInPolygon4(vertices, new Vector2((float)edge.End.X, (float)edge.End.Y))) { edgesToRemove.Add(edge); } } foreach (VEdge edge in edgesToRemove) { voronoi.Remove(edge); } // DEBUG voronoi foreach (VEdge edge in voronoi) { Debug.DrawLine(new Vector3((float)edge.Start.X, 0f, (float)edge.Start.Y), new Vector3((float)edge.End.X, 0f, (float)edge.End.Y), Color.red, 30); } // 3. Turn remaining edges into a graph (create a list of for each point representing the points it is connected to) Dictionary <VPoint, List <VPoint> > voronoiGraph = new Dictionary <VPoint, List <VPoint> >(); foreach (VEdge edge in voronoi) { // handle start point if (!voronoiGraph.ContainsKey(edge.Start)) { voronoiGraph.Add(edge.Start, new List <VPoint>() { edge.End }); } else if (!voronoiGraph[edge.Start].Contains(edge.End)) { voronoiGraph[edge.Start].Add(edge.End); } // handle end point if (!voronoiGraph.ContainsKey(edge.End)) { voronoiGraph.Add(edge.End, new List <VPoint>() { edge.Start }); } else if (!voronoiGraph[edge.End].Contains(edge.Start)) { voronoiGraph[edge.End].Add(edge.Start); } } // 4. Find longest path (with consideration for straightness) between two leaves in graph - this is the centerline Dictionary <VPoint, List <VPoint> > voronoiLeaves = voronoiGraph.Where(x => x.Value.Count == 1).ToDictionary(x => x.Key, x => x.Value); float curvePenalty = 0.00007f; float longestDistance = float.MinValue; List <VPoint> centerLine = new List <VPoint>(); float longestDistanceNoPenalty = float.MinValue; List <VPoint> centerLineNoPenalty = new List <VPoint>(); foreach (KeyValuePair <VPoint, List <VPoint> > startPoint in voronoiLeaves) { foreach (KeyValuePair <VPoint, List <VPoint> > endPoint in voronoiLeaves) { if (startPoint.Key == endPoint.Key) { continue; } List <VPoint> leavesPath = GetShortestPath(new List <VPoint>() { startPoint.Key }, endPoint.Key, voronoiGraph); if (leavesPath == null) { continue; } float distanceWithPenalty = GetPathDistance(leavesPath, curvePenalty); if (distanceWithPenalty > longestDistance) { longestDistance = distanceWithPenalty; centerLine = leavesPath; } float distanceNoPenalty = GetPathDistance(leavesPath, 0f); if (distanceNoPenalty > longestDistanceNoPenalty) { longestDistanceNoPenalty = distanceNoPenalty; centerLineNoPenalty = leavesPath; } } } // If the straight centerline is too short, take the longer curvy one instead if (GetPathDistance(centerLine, 0f) < 0.4f * GetPathDistance(centerLineNoPenalty, 0f)) { centerLine = centerLineNoPenalty; } // 5. Smoothen the centerline int smoothSteps = 5; List <VPoint> smoothCenterLine = new List <VPoint>(); smoothCenterLine.AddRange(centerLine); for (int i = 0; i < smoothSteps; i++) { smoothCenterLine = SmoothLine(smoothCenterLine); } // DEBUG centerline //Debug.Log("Longest path without curve penalty: " + GetPathDistance(centerLineNoPenalty, 0f)); //Debug.Log("Longest path with curve penalty: " + GetPathDistance(centerLine, curvePenalty)); //for (int i = 1; i < centerLineNoPenalty.Count; i++) Debug.DrawLine(new Vector3((float)centerLineNoPenalty[i - 1].X, 0f, (float)centerLineNoPenalty[i - 1].Y), new Vector3((float)centerLineNoPenalty[i].X, 0f, (float)centerLineNoPenalty[i].Y), Color.blue, 30); //for (int i = 1; i < centerLine.Count; i++) Debug.DrawLine(new Vector3((float)centerLine[i - 1].X, 0f, (float)centerLine[i - 1].Y), new Vector3((float)centerLine[i].X, 0f, (float)centerLine[i].Y), Color.red, 30); for (int i = 1; i < smoothCenterLine.Count; i++) { Debug.DrawLine(new Vector3((float)smoothCenterLine[i - 1].X, 0f, (float)smoothCenterLine[i - 1].Y), new Vector3((float)smoothCenterLine[i].X, 0f, (float)smoothCenterLine[i].Y), Color.blue, 30); } // 6. Make sure the path goes from left to right double xChange = 0; for (int i = 1; i < smoothCenterLine.Count; i++) { xChange += smoothCenterLine[i].X - smoothCenterLine[i - 1].X; } if (xChange < 0) { smoothCenterLine.Reverse(); } // 7. Place text along centerline return(DrawTextAlongPath(label, smoothCenterLine, color)); }