// Create area segments borders private void CreateAreaBlockerLines() { foreach (var edge in VoronoiDiagram.Edges) { if (!edge.Visible()) { continue; } int leftAreaSegmentID = _siteAreaSegmentMap[edge.LeftSite.Coord]; int rightAreaSegmentID = _siteAreaSegmentMap[edge.RightSite.Coord]; AreaSegment leftAreaSegment = AreaSegmentGraph.GetNodeData(leftAreaSegmentID); AreaSegment rightAreaSegment = AreaSegmentGraph.GetNodeData(rightAreaSegmentID); var leftNeighborhood = AreaSegmentGraph.GetNeighbours(leftAreaSegmentID); if (leftAreaSegmentID == rightAreaSegmentID || leftAreaSegment.Type == AreaSegment.EAreaSegmentType.Border || rightAreaSegment.Type == AreaSegment.EAreaSegmentType.Border || !leftNeighborhood.Contains(rightAreaSegmentID) || AreaSegmentGraph.GetEdgeValue(leftAreaSegmentID, rightAreaSegmentID) != (int)AreaSegment.EAreaSegmentEdgeType.NonNavigable) { continue; } var p0 = edge.ClippedEnds[LR.LEFT].ToUnityVector2(); var p1 = edge.ClippedEnds[LR.RIGHT].ToUnityVector2(); var segment = new[] { p0, p1 }; AreaBlockerLines.Add(segment); } }
// Checks if segments are neighbours private bool AreNeighbours(IEnumerable <int> areaSegments) { // Check for null if (areaSegments == null || !areaSegments.Any()) { return(false); } // Check if segments are neighbours var found = new List <int> { }; var frontier = new Queue <int>(); frontier.Enqueue(areaSegments.ElementAt(0)); while (found.Count < areaSegments.Count() && frontier.Count > 0) { var element = frontier.Dequeue(); if (areaSegments.Contains(element)) { found.Add(element); foreach (int neighbour in AreaSegmentGraph.GetNeighbours(element)) { frontier.Enqueue(neighbour); } } } return(found.Count == areaSegments.Count()); }
// Assign areas to the base graph based on a specific set of rules private void CreateAreaGraph(Queue <StoryStructure.AreaSegmentRewrite> rewrites) { var rewritesCopy = new Queue <StoryStructure.AreaSegmentRewrite>(rewrites); while (rewrites.Count > 0) { var rule = rewrites.Dequeue(); if (!AreaSegmentGraph.Replace(rule.Pattern, rule.Replace, rule.Correspondences)) { // Try again Debug.LogWarning("Failed to generate map with current seed: " + Random.state); _voronoiSamples += 10; CreateBaseGraph(_lloydIterations); CreateAreaGraph(rewritesCopy); } } // Any empty area is marked as border foreach (var areaSegment in AreaSegmentGraph.GetAllNodeData()) { if (areaSegment.Type == AreaSegment.EAreaSegmentType.Empty) { areaSegment.Type = AreaSegment.EAreaSegmentType.Border; } } }
// Get area data graph that contains the given area segments, with the respective center and polygon, and neighborhood information public Graph <AreaData> GetAreaDataGraph(IEnumerable <int> areaSegments) { if (!AreNeighbours(areaSegments)) { return(null); } // Build polygons and gather roads for each site Dictionary <int, Vector2[]> areaSegmentPolygonMap = new Dictionary <int, Vector2[]>(); Dictionary <int, List <Vector2[]> > areaSegmentRoadMap = new Dictionary <int, List <Vector2[]> >(); foreach (int areaSegment in areaSegments) { var site = new Vector2f(_areaSegmentCenterMap[areaSegment]); areaSegmentPolygonMap.Add(areaSegment, GetSitePolygon(site)); areaSegmentRoadMap.Add(areaSegment, GetSitePaths(site)); } // Build graph Dictionary <int, int> graphMap = new Dictionary <int, int>(); Graph <AreaData> areaDataGraph = new Graph <AreaData>(); foreach (int areaSegment in areaSegments) { AreaData data = new AreaData { Center = _areaSegmentCenterMap[areaSegment], Polygon = areaSegmentPolygonMap[areaSegment], Segment = AreaSegmentGraph.GetNodeData(areaSegment), Paths = areaSegmentRoadMap[areaSegment] }; int newID = areaDataGraph.AddNode(data); graphMap.Add(areaSegment, newID); } foreach (int areaSegment in areaSegments) { foreach (int neighbour in AreaSegmentGraph.GetNeighbours(areaSegment)) { if (areaSegments.Contains(neighbour)) { areaDataGraph.AddEdge(graphMap[areaSegment], graphMap[neighbour], 0); } } } return(areaDataGraph); }
// Get closest AreaSegmentGraph ID center to specified pos private int GetClosestAreaSegmentID(Vector2 pos) { float smallestDistance = float.MaxValue; int closestAreaSegment = 0; foreach (var id in AreaSegmentGraph.GetAllNodeIDs()) { Vector2 center = _areaSegmentCenterMap[id]; float currentDistance = (center - pos).sqrMagnitude; if (currentDistance < smallestDistance) { smallestDistance = currentDistance; closestAreaSegment = id; } } return(closestAreaSegment); }
// Get closest AreaSegment to specified pos private AreaSegment GetClosestAreaSegment(Vector2 pos) { return(AreaSegmentGraph.GetNodeData(GetClosestAreaSegmentID(pos))); }
// Create border blocker polygon private void CreateBorderBlockerLines(float borderInlandOffset) { var segments = new List <KeyValuePair <Edge, Vector2> >(); foreach (var edge in VoronoiDiagram.Edges) { //Check if this edge is visible before continuing if (!edge.Visible()) { continue; } int leftAreaSegmentID = _siteAreaSegmentMap[edge.RightSite.Coord]; int rightAreaSegmentID = _siteAreaSegmentMap[edge.LeftSite.Coord]; AreaSegment leftAreaSegment = AreaSegmentGraph.GetNodeData(leftAreaSegmentID); AreaSegment rightAreaSegment = AreaSegmentGraph.GetNodeData(rightAreaSegmentID); // Either one or the other must be a border type if (leftAreaSegment.Type != rightAreaSegment.Type && (leftAreaSegment.Type == AreaSegment.EAreaSegmentType.Border || rightAreaSegment.Type == AreaSegment.EAreaSegmentType.Border)) { // Add border edge with the biome center to scale inwards later segments.Add(new KeyValuePair <Edge, Vector2>(edge, leftAreaSegment.Type == AreaSegment.EAreaSegmentType.Border ? edge.LeftSite.Coord.ToUnityVector2() : edge.RightSite.Coord.ToUnityVector2())); } } // Group connected segments var edgeGroups = new List <List <KeyValuePair <Edge, Vector2> > >(); while (segments.Count > 0) { var edges = new List <KeyValuePair <Edge, Vector2> >(); var startEdge = segments[0]; segments.Remove(startEdge); Vertex headPoint = startEdge.Key.RightVertex; Vertex tailPoint = startEdge.Key.LeftVertex; edges.Add(startEdge); // Find a polygon var polygonClosed = false; while (!polygonClosed && segments.Count > 0) { for (int i = 0; i < segments.Count; i++) { var currentElement = segments[i]; Vertex leftPoint = currentElement.Key.LeftVertex; Vertex rightPoint = currentElement.Key.RightVertex; if (leftPoint == headPoint) { edges.Add(currentElement); segments.Remove(currentElement); headPoint = rightPoint; } else if (rightPoint == headPoint) { edges.Add(currentElement); segments.Remove(currentElement); headPoint = leftPoint; } // Polygon has been closed if (headPoint == tailPoint) { polygonClosed = true; break; } } } edgeGroups.Add(edges); } // Iterate over each polygon found previously foreach (var edges in edgeGroups) { var polygon = new List <Vector2>(); var borderLines = edges.Select(pair => pair.Key).ToList().EdgesToSortedLines(); foreach (var line in borderLines) { // Offset borders towards biome center var left = line[0]; var right = line[1]; var center = Vector2.zero; edges.ForEach(e => { var l = e.Key.ClippedEnds[LR.LEFT].ToUnityVector2(); var r = e.Key.ClippedEnds[LR.RIGHT].ToUnityVector2(); if ((l == left || l == right) && (r == left || r == right)) { center = e.Value; } }); left += (center - left).normalized * borderInlandOffset; right += (center - right).normalized * borderInlandOffset; // Offsetting can give duplicated points if (!polygon.Contains(left)) { polygon.Add(left); } if (!polygon.Contains(right)) { polygon.Add(right); } } // Create border blocker lines BorderBlockerLines.AddRange(polygon.ToArray().PolygonToLines()); } }
// Create path lines private void CreatePathLines() { var mainPath = new List <Vector2[]>(); var allSidePaths = new List <Vector2[]>(); foreach (var graphEdge in AreaSegmentGraph.GetAllEdges()) { int edgeValue = AreaSegmentGraph.GetEdgeValue(graphEdge); Vector2 leftCenter = _areaSegmentCenterMap[graphEdge.x]; Vector2 rightCenter = _areaSegmentCenterMap[graphEdge.y]; Vector2f leftSite = new Vector2f(leftCenter); Vector2f rightSite = new Vector2f(rightCenter); Vector2 edgeCrossing = Vector2.zero; // Get the middle point of the crossing voronoi edge -> ensure crossable point foreach (var e in VoronoiDiagram.Edges) { if (!e.Visible()) { continue; } if (e.LeftSite.Coord == leftSite && e.RightSite.Coord == rightSite || e.LeftSite.Coord == rightSite && e.RightSite.Coord == leftSite) { edgeCrossing = (e.ClippedEnds[LR.LEFT] + e.ClippedEnds[LR.RIGHT]).ToUnityVector2() / 2f; break; } } // Add path lines switch (edgeValue) { case (int)AreaSegment.EAreaSegmentEdgeType.MainPath: mainPath.Add(new[] { leftCenter, edgeCrossing }); mainPath.Add(new[] { edgeCrossing, rightCenter }); break; case (int)AreaSegment.EAreaSegmentEdgeType.SidePath: allSidePaths.Add(new[] { leftCenter, edgeCrossing }); allSidePaths.Add(new[] { edgeCrossing, rightCenter }); break; } } // Separate side paths var sidePathsList = new List <List <Vector2[]> >(); while (allSidePaths.Count > 0) { var sidePath = new List <Vector2[]>(); sidePath.Add(allSidePaths[0]); allSidePaths.Remove(sidePath[0]); bool found = true; while (found) { found = false; for (int i = 0; i < allSidePaths.Count; i++) { var line = allSidePaths[i]; var predicate = new Func <Vector2[], bool>(a => a[0] == line[0] || a[0] == line[1] || a[1] == line[0] || a[1] == line[1]); if (sidePath.Any(predicate)) { found = true; sidePath.Add(line); allSidePaths.Remove(line); break; } } } sidePathsList.Add(sidePath); } MainPathLines = CreateBezierPath(mainPath); foreach (var sidePath in sidePathsList) { SidePathLines.AddRange(CreateBezierPath(sidePath)); } }
// Create a graph containing all connected empty areas private void CreateBaseGraph(int lloydIterations) { // Create uniform random point distribution and Voronoi Diagram var centers = new List <Vector2f>(); for (int i = 0; i < _voronoiSamples; i++) { var x = Random.Range(0f, MapSize); var y = Random.Range(0f, MapSize); centers.Add(new Vector2f(x, y)); } VoronoiDiagram = new Voronoi(centers, new Rectf(0, 0, MapSize, MapSize)); // Apply Lloyd Relaxation VoronoiDiagram.LloydRelaxation(lloydIterations); // Assign area segments to initial areas foreach (var site in VoronoiDiagram.SiteCoords()) { bool isOnBorder = false; var segments = VoronoiDiagram.VoronoiBoundaryForSite(site); foreach (var segment in segments) { if (!(segment.p0.x <= VoronoiDiagram.PlotBounds.left) && !(segment.p0.x >= VoronoiDiagram.PlotBounds.right) && !(segment.p0.y <= VoronoiDiagram.PlotBounds.top) && !(segment.p0.y >= VoronoiDiagram.PlotBounds.bottom) && !(segment.p1.x <= VoronoiDiagram.PlotBounds.left) && !(segment.p1.x >= VoronoiDiagram.PlotBounds.right) && !(segment.p1.y <= VoronoiDiagram.PlotBounds.top) && !(segment.p1.y >= VoronoiDiagram.PlotBounds.bottom)) { continue; } isOnBorder = true; break; } // Assign areaSegment to site and corresponding area var areaSegment = new AreaSegment(isOnBorder ? AreaSegment.EAreaSegmentType.Border : AreaSegment.EAreaSegmentType.Empty); var nodeID = AreaSegmentGraph.AddNode(areaSegment); _siteAreaSegmentMap.Add(site, nodeID); _areaSegmentCenterMap.Add(nodeID, site.ToUnityVector2()); } // Create navigation graph - for each area segment that is not a border, add reachable neighbors foreach (var id in _siteAreaSegmentMap) { var areaSegment = AreaSegmentGraph.GetNodeData(id.Value); if (areaSegment.Type == AreaSegment.EAreaSegmentType.Border) { continue; } Vector2 center = _areaSegmentCenterMap[id.Value]; foreach (var neighbor in VoronoiDiagram.NeighborSitesForSite(new Vector2f(center.x, center.y))) { var neighborSegment = AreaSegmentGraph.GetNodeData(_siteAreaSegmentMap[neighbor]); if (neighborSegment.Type != AreaSegment.EAreaSegmentType.Border) { AreaSegmentGraph.AddEdge(_siteAreaSegmentMap[neighbor], id.Value, (int)AreaSegment.EAreaSegmentEdgeType.NonNavigable); } } } }