/// <summary> /// Used for creating artificial corners, i.e. ones that are forced /// instead of created through a DelaunayTriangle. /// </summary> /// <param name="pos"></param> /// <param name="cornerNum"></param> public Corner(Vector2 pos, int cornerNum, bool mapCorner = false) : base(pos) { num = cornerNum; isOOB = !VoronoiGenerator.IsInMapBounds(position); isMapCorner = mapCorner; isOnBorder = isMapCorner; }
private void CreateRNG() { if (newRandomSeed) { randomSeed = System.DateTime.Now.Ticks.ToString(); } instance = this; rng = new Random(randomSeed.GetHashCode()); }
/// <summary> /// Based off of https://www.redblobgames.com/maps/noisy-edges/ /// </summary> public void CreateNoisyEdge(int subdivisions) { if (segments != null) // already noisified this edge { return; } if (polygons.Count != 2) { // probably on the map edge if (polygons.Count > 2 || polygons.Count == 0) { throw new Exception("Invalid polygon count in edge " + id + ". Count: " + polygons.Count); } CreateSimpleBorder(); return; } segments = new List <Vector3>(); pairedEdge = polygons[0].centroid.GetConnectingEdge(polygons[1].centroid); if (pairedEdge == null) { throw new Exception("Invalid delaunay edge"); } Vector3 control1 = pairedEdge.start.position; Vector3 control2 = pairedEdge.end.position; if (!VoronoiGenerator.TryGetLineIntersections( control1, control2, start.position, end.position, out Vector2 intersectPoint, out float tMid, out float t2)) { Debug.LogWarning("SPECIAL CASE: voronoi and delaunay edges do not meet"); CreateSimpleBorder(); return; } segments.Add(start.position); segments.AddRange( CreateSegments(start.position, end.position, control1, control2, subdivisions, -tMid)); segments.Add(end.position); }
private List <Vector3> CreateSegments( Vector3 lineStart, Vector3 lineEnd, Vector3 control1, Vector3 control2, int subdivisions, float tMid) { List <Vector3> newSegments = new List <Vector3>(); if (subdivisions > 0) { float lineDist = Vector3.Distance(lineStart, lineEnd); float controlDist = Vector3.Distance(control1, control2); if (lineDist < controlDist) { // clamp the control points to stop ugly extreme lines float diff = (controlDist - lineDist) * .5f; Vector3 newControl1 = Vector3.MoveTowards(control1, control2, diff); Vector3 newControl2 = Vector3.MoveTowards(control2, control1, diff); control1 = newControl1; control2 = newControl2; } Vector3 edgeCenter1 = (lineStart + control1) * .5f; Vector3 edgeCenter2 = (lineStart + control2) * .5f; Vector3 midPoint = Vector3.Lerp(control1, control2, VoronoiGenerator.GetNewT(tMid, isRiver)); newSegments.AddRange( CreateSegments(lineStart, midPoint, edgeCenter1, edgeCenter2, subdivisions - 1, tMid)); newSegments.Add(midPoint); Vector3 edgeCenter3 = (lineEnd + control1) * .5f; Vector3 edgeCenter4 = (lineEnd + control2) * .5f; newSegments.AddRange( CreateSegments(midPoint, lineEnd, edgeCenter3, edgeCenter4, subdivisions - 1, tMid)); } return(newSegments); }
/// <summary> /// Some random seeds produce invalid polygon that uses map corners. Ex: /// 637427950365396994 /// 637427958541256155 /// </summary> /// <param name="vGen"></param> /// <param name="dGraph"></param> public VoronoiGraph(VoronoiGenerator vGen, DelaunayGraph dGraph) { generator = vGen; delaunayGraph = dGraph; cornerCount = 0; uniqueVEdges = new HashSet <VEdge>(); uniqueCorners = new HashSet <Corner>(); removeCorners = new List <Corner>(); mapCorners = new Dictionary <byte, Corner>() { [VoronoiGenerator.TopRightCornerByte] = new Corner(VoronoiGenerator.topRight, cornerCount++, true), [VoronoiGenerator.TopLeftCornerByte] = new Corner(VoronoiGenerator.topLeft, cornerCount++, true), [VoronoiGenerator.BottomRightCornerByte] = new Corner(VoronoiGenerator.bottomRight, cornerCount++, true), [VoronoiGenerator.BottomLeftCornerByte] = new Corner(VoronoiGenerator.bottomLeft, cornerCount++, true), }; invalidatedPolygons = new List <Polygon>(); invalidatedEdges = new HashSet <VEdge>(); polygons = new List <Polygon>(); for (int i = 0; i < dGraph.centroids.Count; ++i) // first 4 centroids are map corners { Polygon poly = new Polygon(dGraph.centroids[i]); polygons.Add(poly); } if (polygons[0].corners == null || polygons[1].corners == null || polygons[2].corners == null || polygons[3].corners == null) { logMsgs = new List <string>(); logMsgs.Add("****** Error found on Seed: " + generator.randomSeed + "******"); logMsgs.Add("Map dimensions: " + generator.mapWidth + ", " + generator.mapHeight); logMsgs.Add("Region amt: " + generator.regionAmount); logMsgs.Add("MinSqrDistBtwnSites: " + generator.minSqrDistBtwnSites); logMsgs.Add("MinDistBtwnSiteAndBorder: " + generator.minDistBtwnSiteAndBorder); logMsgs.Add("MinDistBtwnCornerAndBorder: " + generator.minDistBtwnCornerAndBorder); logMsgs.Add("MinEdgeLengthToMerge: " + generator.minEdgeLengthToMerge); logMsgs.Add("Merge near corners: " + generator.mergeNearCorners); logMsgs.Add("******************************************\n"); logMsgs.Add("Corner polygon did not make it passed initial generation"); System.IO.File.WriteAllLines(logFilePath + "BorderCornerDeletedIssue_" + generator.randomSeed + ".txt", logMsgs); Log("Corner polygon did not make it passed initial generation", LogType.Exception, false); } VoronoiHelper.Associate(polygons[0], mapCorners[TopLeftCornerByte]); VoronoiHelper.Associate(polygons[1], mapCorners[TopRightCornerByte]); VoronoiHelper.Associate(polygons[2], mapCorners[BottomRightCornerByte]); VoronoiHelper.Associate(polygons[3], mapCorners[BottomLeftCornerByte]); if (VoronoiGenerator.MergeNearCorners) { MergeNearCorners(); } if (generator.clampToMapBounds) { if (!ClampToMapBounds()) { return; } foreach (Corner corner in uniqueCorners) { if (corner.isInvalidated) { if (!removeCorners.Contains(corner)) { removeCorners.Add(corner); } continue; } if (corner.isOOB) { removeCorners.Add(corner); } } } for (int i = removeCorners.Count - 1; i >= 0; --i) { Corner removingCorner = removeCorners[i]; for (int j = removingCorner.connectedEdges.Count - 1; j >= 0; --j) { VEdge edge = removingCorner.connectedEdges[j]; removingCorner.connectedEdges.Remove(edge); uniqueVEdges.Remove(edge); Corner opposite = edge.GetOppositeSite(removingCorner); opposite.connectedEdges.Remove(edge); foreach (var poly in removingCorner.polygons) { poly.Remove(edge); } } removingCorner.connectedEdges.Clear(); RemoveCorner(removingCorner); } int culled = 0; foreach (var polygon in invalidatedPolygons) { debugPolygons.Add(polygon); polygons.Remove(polygon); ++culled; } foreach (Polygon polygon in polygons) { polygon.CenterCentroid(); polygon.SortEdges(); } }
public void GenerateMap() { instance = this; DestroyImmediate(lr); ClearMap(); CreateRNG(); MergeNearCorners = mergeNearCorners; minSqrDistBetweenCorners = minSqrDistBtwnSites; minDistCornerAndBorder = minDistBtwnCornerAndBorder; mapBounds = new Rect(0, 0, mapWidth, mapHeight); topLeft = new Vector2(mapBounds.xMin, mapBounds.yMax); topRight = new Vector2(mapBounds.xMax, mapBounds.yMax); bottomRight = new Vector2(mapBounds.xMax, mapBounds.yMin); bottomLeft = new Vector2(mapBounds.xMin, mapBounds.yMin); borderEndPoints = new Dictionary <MapSide, Tuple <Vector2, Vector2> >(); borderEndPoints.Add(MapSide.Top, new Tuple <Vector2, Vector2>(topLeft, topRight)); borderEndPoints.Add(MapSide.Right, new Tuple <Vector2, Vector2>(topRight, bottomRight)); borderEndPoints.Add(MapSide.Bottom, new Tuple <Vector2, Vector2>(bottomRight, bottomLeft)); borderEndPoints.Add(MapSide.Left, new Tuple <Vector2, Vector2>(bottomLeft, topLeft)); List <Vector2> sites = new List <Vector2>(); // create corner sites sites.Add(topLeft); sites.Add(topRight); sites.Add(bottomRight); sites.Add(bottomLeft); int retryAttempts = 0; for (int i = 0; i < regionAmount; i++) { Vector2 site = new Vector2( (float)(minDistBtwnSiteAndBorder + rng.NextDouble() * (mapHeight - minDistBtwnSiteAndBorder * 2)), (float)(minDistBtwnSiteAndBorder + rng.NextDouble() * (mapWidth - minDistBtwnSiteAndBorder * 2))); if (IsTooNear(sites, site)) { // try again ++retryAttempts; if (retryAttempts > 25) { Debug.Log("Unable to place new site within exceptable distance parameters." + "\nAborting creating new sites. Created " + i + " out of " + regionAmount); break; } --i; } else { sites.Add(site); } } try { dGraph = new DelaunayGraph(sites); vGraph = new VoronoiGraph(this, dGraph); CreateRegions(); } catch (System.Exception ex) { newRandomSeed = false; // make sure we stay on this seed until the problem has been rectified Debug.LogException(ex); } }
public Corner(DelaunayTriangle triangle, int cornerNum) : base(triangle.realCenter) { this.delaunayTriangle = triangle; num = cornerNum; isOOB = !VoronoiGenerator.IsInMapBounds(position); }