private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null) { if (cell == null) cell = edge.AdjacentCell(null); if (cell == null) return Vector2.UnitX; CompareCCW compare = new CompareCCW(cell.Center); if (compare.Compare(edge.Point1, edge.Point2) == -1) { var temp = edge.Point1; edge.Point1 = edge.Point2; edge.Point2 = temp; } Vector2 normal = Vector2.Zero; normal = Vector2.Normalize(edge.Point2 - edge.Point1); Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2); normal = new Vector2(-normal.Y, normal.X); if (Vector2.Dot(normal, diffToCell) < 0) { normal = -normal; } return normal; }
public static List <VertexPositionTexture> GenerateWallEdgeVertices(List <VoronoiCell> cells, Level level, float zCoord) { float outWardThickness = level.GenerationParams.WallEdgeExpandOutwardsAmount; List <VertexPositionTexture> vertices = new List <VertexPositionTexture>(); foreach (VoronoiCell cell in cells) { Vector2 minVert = cell.Edges[0].Point1; Vector2 maxVert = cell.Edges[0].Point1; float circumference = 0.0f; foreach (GraphEdge edge in cell.Edges) { circumference += Vector2.Distance(edge.Point1, edge.Point2); minVert = new Vector2( Math.Min(minVert.X, edge.Point1.X), Math.Min(minVert.Y, edge.Point1.Y)); maxVert = new Vector2( Math.Max(maxVert.X, edge.Point1.X), Math.Max(maxVert.Y, edge.Point1.Y)); } Vector2 center = (minVert + maxVert) / 2; foreach (GraphEdge edge in cell.Edges) { if (!edge.IsSolid) { continue; } GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2))); var leftAdjacentCell = leftEdge?.AdjacentCell(cell); if (leftAdjacentCell != null) { var adjEdge = leftAdjacentCell.Edges.Find(e => e != leftEdge && e.IsSolid && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2))); if (adjEdge != null) { leftEdge = adjEdge; } } GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2))); var rightAdjacentCell = rightEdge?.AdjacentCell(cell); if (rightAdjacentCell != null) { var adjEdge = rightAdjacentCell.Edges.Find(e => e != rightEdge && e.IsSolid && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2))); if (adjEdge != null) { rightEdge = adjEdge; } } Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero; float inwardThickness1 = level.GenerationParams.WallEdgeExpandInwardsAmount; float inwardThickness2 = level.GenerationParams.WallEdgeExpandInwardsAmount; if (leftEdge != null && !leftEdge.IsSolid) { leftNormal = edge.Point1.NearlyEquals(leftEdge.Point1) ? Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) : Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2); } else if (leftEdge != null) { leftNormal = -Vector2.Normalize(edge.GetNormal(cell) + leftEdge.GetNormal(leftAdjacentCell ?? cell)); if (!MathUtils.IsValid(leftNormal)) { leftNormal = -edge.GetNormal(cell); } } else { leftNormal = Vector2.Normalize(cell.Center - edge.Point1); } inwardThickness1 = Math.Min(Vector2.Distance(edge.Point1, cell.Center), inwardThickness1); if (!MathUtils.IsValid(leftNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid left normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { if (GameMain.World.BodyList.Contains(cell.Body)) { GameMain.World.Remove(cell.Body); } cell.Body = null; } leftNormal = Vector2.UnitX; break; } if (rightEdge != null && !rightEdge.IsSolid) { rightNormal = edge.Point2.NearlyEquals(rightEdge.Point1) ? Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) : Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2); } else if (rightEdge != null) { rightNormal = -Vector2.Normalize(edge.GetNormal(cell) + rightEdge.GetNormal(rightAdjacentCell ?? cell)); if (!MathUtils.IsValid(rightNormal)) { rightNormal = -edge.GetNormal(cell); } } else { rightNormal = Vector2.Normalize(cell.Center - edge.Point2); } inwardThickness2 = Math.Min(Vector2.Distance(edge.Point2, cell.Center), inwardThickness2); if (!MathUtils.IsValid(rightNormal)) { #if DEBUG DebugConsole.ThrowError("Invalid right normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) { if (GameMain.World.BodyList.Contains(cell.Body)) { GameMain.World.Remove(cell.Body); } cell.Body = null; } rightNormal = Vector2.UnitX; break; } float point1UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point1 - center)); float point2UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point2 - center)); //handle wrapping around 0/360 if (point1UV - point2UV > MathHelper.Pi) { point1UV -= MathHelper.TwoPi; } int textureRepeatCount = (int)Math.Max(circumference / 2 / level.GenerationParams.WallEdgeTextureWidth, 1); point1UV = point1UV / MathHelper.TwoPi * textureRepeatCount; point2UV = point2UV / MathHelper.TwoPi * textureRepeatCount; for (int i = 0; i < 2; i++) { Vector2[] verts = new Vector2[3]; VertexPositionTexture[] vertPos = new VertexPositionTexture[3]; if (i == 0) { verts[0] = edge.Point1 - leftNormal * outWardThickness; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point1 + leftNormal * inwardThickness1; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 0.0f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point1UV, 1.0f)); } else { verts[0] = edge.Point1 + leftNormal * inwardThickness1; verts[1] = edge.Point2 - rightNormal * outWardThickness; verts[2] = edge.Point2 + rightNormal * inwardThickness2; vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 1.0f)); vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f)); vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point2UV, 1.0f)); } vertices.AddRange(vertPos); } } } return(vertices); }
public static List <VoronoiCell> CarveCave(List <VoronoiCell> cells, Vector2 startPoint, out List <VoronoiCell> newCells) { Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); float siteInterval = 400.0f; float siteVariance = siteInterval * 0.4f; Vector4 edges = new Vector4( cells.Min(x => x.edges.Min(e => e.point1.X)), cells.Min(x => x.edges.Min(e => e.point1.Y)), cells.Max(x => x.edges.Max(e => e.point1.X)), cells.Max(x => x.edges.Max(e => e.point1.Y))); edges.X -= siteInterval * 2; edges.Y -= siteInterval * 2; edges.Z += siteInterval * 2; edges.W += siteInterval * 2; Rectangle borders = new Rectangle((int)edges.X, (int)edges.Y, (int)(edges.Z - edges.X), (int)(edges.W - edges.Y)); for (float x = edges.X + siteInterval; x < edges.Z - siteInterval; x += siteInterval) { for (float y = edges.Y + siteInterval; y < edges.W - siteInterval; y += siteInterval) { if (Rand.Int(5, false) == 0) { continue; //skip some positions to make the cells more irregular } sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, false)); } } List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, edges.X, edges.Y, edges.Z, edges.W); List <VoronoiCell>[,] cellGrid; newCells = GraphEdgesToCells(graphEdges, borders, 1000, out cellGrid); foreach (VoronoiCell cell in newCells) { //if the cell is at the edge of the graph, remove it if (cell.edges.Any(e => e.point1.X == edges.X || e.point1.X == edges.Z || e.point1.Y == edges.Z || e.point1.Y == edges.W)) { cell.CellType = CellType.Removed; continue; } //remove cells that aren't inside any of the original "base cells" if (cells.Any(c => c.IsPointInside(cell.Center))) { continue; } foreach (GraphEdge edge in cell.edges) { //mark all the cells adjacent to the removed cell as edges of the cave var adjacent = edge.AdjacentCell(cell); if (adjacent != null && adjacent.CellType != CellType.Removed) { adjacent.CellType = CellType.Edge; } } cell.CellType = CellType.Removed; } newCells.RemoveAll(newCell => newCell.CellType == CellType.Removed); //start carving from the edge cell closest to the startPoint VoronoiCell startCell = null; float closestDist = 0.0f; foreach (VoronoiCell cell in newCells) { if (cell.CellType != CellType.Edge) { continue; } float dist = Vector2.Distance(startPoint, cell.Center); if (dist < closestDist || startCell == null) { startCell = cell; closestDist = dist; } } startCell.CellType = CellType.Path; List <VoronoiCell> path = new List <VoronoiCell>() { startCell }; VoronoiCell pathCell = startCell; for (int i = 0; i < newCells.Count / 2; i++) { var allowedNextCells = new List <VoronoiCell>(); foreach (GraphEdge edge in pathCell.edges) { var adjacent = edge.AdjacentCell(pathCell); if (adjacent == null || adjacent.CellType == CellType.Removed || adjacent.CellType == CellType.Edge) { continue; } allowedNextCells.Add(adjacent); } if (allowedNextCells.Count == 0) { if (i > 5) { break; } foreach (GraphEdge edge in pathCell.edges) { var adjacent = edge.AdjacentCell(pathCell); if (adjacent == null || adjacent.CellType == CellType.Removed) { continue; } allowedNextCells.Add(adjacent); } if (allowedNextCells.Count == 0) { break; } } //randomly pick one of the adjacent cells as the next cell pathCell = allowedNextCells[Rand.Int(allowedNextCells.Count, false)]; //randomly take steps further away from the startpoint to make the cave expand further if (Rand.Int(4, false) == 0) { float furthestDist = 0.0f; foreach (VoronoiCell nextCell in allowedNextCells) { float dist = Vector2.Distance(startCell.Center, nextCell.Center); if (dist > furthestDist || furthestDist == 0.0f) { furthestDist = dist; pathCell = nextCell; } } } pathCell.CellType = CellType.Path; path.Add(pathCell); } //make sure the tunnel is always wider than minPathWidth float minPathWidth = 100.0f; for (int i = 0; i < path.Count; i++) { var cell = path[i]; foreach (GraphEdge edge in cell.edges) { if (edge.point1 == edge.point2) { continue; } if (Vector2.Distance(edge.point1, edge.point2) > minPathWidth) { continue; } GraphEdge adjacentEdge = cell.edges.Find(e => e != edge && (e.point1 == edge.point1 || e.point2 == edge.point1)); var adjacentCell = adjacentEdge.AdjacentCell(cell); if (i > 0 && (adjacentCell.CellType == CellType.Path || adjacentCell.CellType == CellType.Edge)) { continue; } adjacentCell.CellType = CellType.Path; path.Add(adjacentCell); } } return(path); }