2 using FarseerPhysics.Collision.Shapes;
3 using FarseerPhysics.Common;
4 using FarseerPhysics.Dynamics;
5 using Microsoft.Xna.Framework;
7 using System.Collections.Generic;
8 using System.Diagnostics;
14 static partial class CaveGenerator
16 public static List<VoronoiCell> GraphEdgesToCells(List<GraphEdge> graphEdges, Rectangle borders,
float gridCellSize, out List<VoronoiCell>[,] cellGrid)
18 List<VoronoiCell> cells =
new List<VoronoiCell>();
20 cellGrid =
new List<VoronoiCell>[(int)Math.Ceiling(borders.Width / gridCellSize), (int)Math.Ceiling(borders.Height / gridCellSize)];
21 for (
int x = 0; x < borders.Width / gridCellSize; x++)
23 for (
int y = 0; y < borders.Height / gridCellSize; y++)
25 cellGrid[x, y] =
new List<VoronoiCell>();
31 if (Vector2.DistanceSquared(ge.
Point1, ge.Point2) < 0.001f) {
continue; }
33 for (
int i = 0; i < 2; i++)
35 Site site = (i == 0) ? ge.
Site1 : ge.Site2;
37 int x = (
int)(Math.Floor((site.
Coord.
X - borders.X) / gridCellSize));
38 int y = (int)(Math.Floor((site.
Coord.Y - borders.Y) / gridCellSize));
40 x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0) - 1);
41 y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1) - 1);
43 VoronoiCell cell = cellGrid[x, y].Find(c => c.Site == site);
48 cellGrid[x, y].Add(cell);
65 foreach (var cell
in cells)
67 Vector2? point1 =
null, point2 =
null;
70 if (MathUtils.NearlyEqual(ge.
Point1.X, borders.X) || MathUtils.NearlyEqual(ge.
Point1.X, borders.Right) ||
71 MathUtils.NearlyEqual(ge.
Point1.Y, borders.Y) || MathUtils.NearlyEqual(ge.
Point1.Y, borders.Bottom))
77 else if (point2 ==
null)
79 if (MathUtils.NearlyEqual(point1.Value, ge.
Point1)) {
continue; }
83 if (MathUtils.NearlyEqual(ge.Point2.X, borders.X) || MathUtils.NearlyEqual(ge.Point2.X, borders.Right) ||
84 MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
92 if (MathUtils.NearlyEqual(point1.Value, ge.Point2)) {
continue; }
96 if (point1.HasValue && point2.HasValue)
98 Debug.Assert(point1 != point2);
99 bool point1OnSide = MathUtils.NearlyEqual(point1.Value.X, borders.X) || MathUtils.NearlyEqual(point1.Value.X, borders.Right);
100 bool point2OnSide = MathUtils.NearlyEqual(point2.Value.X, borders.X) || MathUtils.NearlyEqual(point2.Value.X, borders.Right);
103 if (point1OnSide != point2OnSide)
105 Vector2 cornerPos =
new Vector2(
106 point1.Value.X < borders.Center.X ? borders.X : borders.Right,
107 point1.Value.Y < borders.Center.Y ? borders.Y : borders.Bottom);
128 new GraphEdge(point1.Value, point2.Value)
144 public static void GeneratePath(Level.Tunnel tunnel, Level level)
146 var targetCells =
new List<VoronoiCell>();
147 for (
int i = 0; i < tunnel.Nodes.Count; i++)
149 var closestCell = level.GetClosestCell(tunnel.Nodes[i].ToVector2());
150 if (closestCell !=
null && !targetCells.Contains(closestCell))
152 targetCells.Add(closestCell);
155 tunnel.Cells.AddRange(GeneratePath(targetCells, level.GetAllCells()));
158 public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells)
160 List<VoronoiCell> pathCells =
new List<VoronoiCell>();
162 if (targetCells.Count == 0) {
return pathCells; }
166 pathCells.Add(currentCell);
168 int currentTargetIndex = 0;
170 int iterationsLeft = cells.Count / 2;
176 double smallestDist =
double.PositiveInfinity;
177 for (
int i = 0; i < currentCell.
Edges.Count; i++)
179 var adjacentCell = currentCell.
Edges[i].AdjacentCell(currentCell);
180 if (adjacentCell ==
null) {
continue; }
181 double dist = MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
182 dist += MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, currentCell.
Site.
Coord.
X, currentCell.
Site.
Coord.Y) * 0.5f;
185 if (Vector2.DistanceSquared(currentCell.
Edges[i].Point1, currentCell.
Edges[i].Point2) < 150.0f * 150.0f)
190 dist *= 10.0f / Math.Max(pathCells.Count(c => c == currentCell), 1.0f);
192 if (dist < smallestDist)
199 currentCell = currentCell.
Edges[edgeIndex].AdjacentCell(currentCell);
201 pathCells.Add(currentCell);
205 if (currentCell == targetCells[currentTargetIndex])
207 currentTargetIndex++;
208 if (currentTargetIndex >= targetCells.Count) {
break; }
211 }
while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
220 public static void RoundCell(
VoronoiCell cell,
float minEdgeLength = 500.0f,
float roundingAmount = 0.5f,
float irregularity = 0.1f)
222 List<GraphEdge> tempEdges =
new List<GraphEdge>();
231 Vector2 edgeDiff = edge.Point2 - edge.
Point1;
232 Vector2 edgeDir = Vector2.Normalize(edgeDiff);
237 if (adjacentEmptyCell?.
CellType ==
CellType.Solid) { adjacentEmptyCell =
null; }
238 if (adjacentEmptyCell !=
null)
242 foreach (
GraphEdge otherEdge
in adjacentEmptyCell.Edges)
245 Vector2 otherEdgeDir = Vector2.Normalize(otherEdge.Point2 - otherEdge.
Point1);
247 if (Math.Abs(Vector2.Dot(otherEdgeDir, edgeDir)) > 0.7f)
249 adjacentEdge = otherEdge;
253 if (adjacentEdge !=
null)
259 List<Vector2> edgePoints =
new List<Vector2>();
260 Vector2 edgeNormal = edge.
GetNormal(cell);
262 float edgeLength = Vector2.Distance(edge.
Point1, edge.Point2);
263 int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
264 for (
int i = 0; i <= pointCount; i++)
268 edgePoints.Add(edge.
Point1);
270 else if (i == pointCount)
272 edgePoints.Add(edge.Point2);
277 float centerF = 0.5f - Math.Abs(0.5f - (i / (
float)pointCount));
278 float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.ServerAndClient);
279 Vector2 extrudedPoint =
281 edgeDiff * (i / (float)pointCount) +
282 edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
284 var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
285 bool isInside =
false;
286 foreach (var nearbyCell
in nearbyCells)
288 if (nearbyCell == cell || nearbyCell.CellType !=
CellType.Solid) {
continue; }
290 if (nearbyCell.IsPointInside(extrudedPoint))
296 Vector2 triangleCenter = (edge.
Point1 + edge.Point2 + extrudedPoint) / 3;
297 foreach (
GraphEdge nearbyEdge
in nearbyCell.Edges)
299 if (!MathUtils.LineSegmentsIntersect(nearbyEdge.
Point1, triangleCenter, edge.
Point1, extrudedPoint) &&
300 !MathUtils.LineSegmentsIntersect(nearbyEdge.
Point1, triangleCenter, edge.Point2, extrudedPoint) &&
301 !MathUtils.LineSegmentsIntersect(nearbyEdge.
Point1, triangleCenter, edge.
Point1, edge.Point2))
307 if (isInside) {
break; }
312 edgePoints.Add(extrudedPoint);
317 for (
int i = 0; i < edgePoints.Count - 1; i++)
319 tempEdges.Add(
new GraphEdge(edgePoints[i], edgePoints[i + 1])
328 NextToMainPath = edge.NextToMainPath,
329 NextToSidePath = edge.NextToSidePath
334 cell.
Edges = tempEdges;
337 public static Body GeneratePolygons(List<VoronoiCell> cells, Level level, out List<Vector2[]> renderTriangles)
339 renderTriangles =
new List<Vector2[]>();
341 List<Vector2> tempVertices =
new List<Vector2>();
342 List<Vector2> bodyPoints =
new List<Vector2>();
344 Body cellBody =
new Body()
346 SleepingAllowed =
false,
347 BodyType = BodyType.Static,
348 CollisionCategories = Physics.CollisionLevel
350 GameMain.World.Add(cellBody, findNewContacts:
false);
352 for (
int n = cells.Count - 1; n >= 0; n-- )
357 tempVertices.Clear();
360 if (Vector2.DistanceSquared(ge.
Point1, ge.Point2) < 0.01f)
continue;
361 if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.
Point1, v) < 1.0f))
363 tempVertices.Add(ge.
Point1);
364 bodyPoints.Add(ge.
Point1);
366 if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.Point2, v) < 1.0f))
368 tempVertices.Add(ge.Point2);
369 bodyPoints.Add(ge.Point2);
373 if (tempVertices.Count < 3 || bodyPoints.Count < 2)
379 Vector2 minVert = tempVertices[0];
380 Vector2 maxVert = tempVertices[0];
381 foreach (var vert
in tempVertices)
383 minVert =
new Vector2(
384 Math.Min(minVert.X, vert.X),
385 Math.Min(minVert.Y, vert.Y));
386 maxVert =
new Vector2(
387 Math.Max(maxVert.X, vert.X),
388 Math.Max(maxVert.Y, vert.Y));
390 Vector2 center = (minVert + maxVert) / 2;
391 renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, center));
393 if (bodyPoints.Count < 2) {
continue; }
395 if (bodyPoints.Count < 3)
397 foreach (Vector2 vertex
in tempVertices)
399 if (bodyPoints.Contains(vertex)) {
continue; }
400 bodyPoints.Add(vertex);
405 for (
int i = 0; i < bodyPoints.Count; i++)
408 bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
413 cellBody.UserData = cell;
414 var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(center));
416 for (
int i = 0; i < triangles.Count; i++)
420 Vector2 a = triangles[i][0];
421 Vector2 b = triangles[i][1];
422 Vector2 c = triangles[i][2];
423 float area = Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y)) / 2.0f;
424 if (area < 1.0f) {
continue; }
426 Vertices bodyVertices =
new Vertices(triangles[i]);
427 PolygonShape polygon =
new PolygonShape(bodyVertices, 5.0f);
428 Fixture fixture =
new Fixture(polygon,
429 Physics.CollisionLevel,
430 Physics.CollisionAll)
434 cellBody.Add(fixture, resetMassData:
false);
436 if (fixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon)
438 DebugConsole.ThrowError(
"Invalid triangle created by CaveGenerator (" + triangles[i][0] +
", " + triangles[i][1] +
", " + triangles[i][2] +
")");
439 GameAnalyticsManager.AddErrorEventOnce(
440 "CaveGenerator.GeneratePolygons:InvalidTriangle",
441 GameAnalyticsManager.ErrorSeverity.Warning,
442 "Invalid triangle created by CaveGenerator (" + triangles[i][0] +
", " + triangles[i][1] +
", " + triangles[i][2] +
"). Seed: " + level.Seed);
445 cell.
Body = cellBody;
447 cellBody.ResetMassData();
452 public static List<Vector2> CreateRandomChunk(
float radius,
int vertexCount,
float radiusVariance)
454 return CreateRandomChunk(radius * 2, radius * 2, vertexCount, radiusVariance);
457 public static List<Vector2> CreateRandomChunk(
float width,
float height,
int vertexCount,
float radiusVariance)
459 Debug.Assert(radiusVariance < Math.Min(width, height));
460 Debug.Assert(vertexCount >= 3);
462 List<Vector2> verts =
new List<Vector2>();
463 float angleStep = MathHelper.TwoPi / vertexCount;
465 for (
int i = 0; i < vertexCount; i++)
467 Vector2 dir =
new Vector2((
float)Math.Cos(angle), (
float)Math.Sin(angle));
468 verts.Add(
new Vector2(dir.X * width / 2, dir.Y * height / 2) + dir * Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.ServerAndClient));
VoronoiCell AdjacentCell(VoronoiCell cell)
Vector2 GetNormal(VoronoiCell cell)
Returns the normal of the edge that points outwards from the specified cell
List< Vector2 > BodyVertices