1
0

PathfindingSystem.Splines.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using Robust.Shared.Collections;
  2. using Robust.Shared.Random;
  3. namespace Content.Server.NPC.Pathfinding;
  4. public sealed partial class PathfindingSystem
  5. {
  6. public record struct SimplifyPathArgs
  7. {
  8. public Vector2i Start;
  9. public Vector2i End;
  10. public List<Vector2i> Path;
  11. }
  12. public record struct SplinePathResult()
  13. {
  14. public static SplinePathResult NoPath = new();
  15. public List<Vector2i> Points = new();
  16. public List<Vector2i> Path = new();
  17. public Dictionary<Vector2i, Vector2i>? CameFrom;
  18. }
  19. public record struct SplinePathArgs(SimplePathArgs Args)
  20. {
  21. public SimplePathArgs Args = Args;
  22. public float MaxRatio = 0.25f;
  23. /// <summary>
  24. /// Minimum distance between subdivisions.
  25. /// </summary>
  26. public int Distance = 20;
  27. }
  28. /// <summary>
  29. /// Gets a spline path from start to end.
  30. /// </summary>
  31. public SplinePathResult GetSplinePath(SplinePathArgs args, Random random)
  32. {
  33. var start = args.Args.Start;
  34. var end = args.Args.End;
  35. var path = new List<Vector2i>();
  36. var pairs = new ValueList<(Vector2i Start, Vector2i End)> { (start, end) };
  37. var subdivided = true;
  38. // Sub-divide recursively
  39. while (subdivided)
  40. {
  41. // Sometimes we might inadvertantly get 2 nodes too close together so better to just check each one as it comes up instead.
  42. var i = 0;
  43. subdivided = false;
  44. while (i < pairs.Count)
  45. {
  46. var pointA = pairs[i].Start;
  47. var pointB = pairs[i].End;
  48. var vector = pointB - pointA;
  49. var halfway = vector / 2f;
  50. // Finding the point
  51. var adj = halfway.Length();
  52. // Should we even subdivide.
  53. if (adj <= args.Distance)
  54. {
  55. // Just check the next entry no double skip.
  56. i++;
  57. continue;
  58. }
  59. subdivided = true;
  60. var opposite = args.MaxRatio * adj;
  61. var hypotenuse = MathF.Sqrt(MathF.Pow(adj, 2) + MathF.Pow(opposite, 2));
  62. // Okay so essentially we have 2 points and no poly
  63. // We add 2 other points to form a diamond and want some point halfway between randomly offset.
  64. var angle = new Angle(MathF.Atan(opposite / adj));
  65. var pointAPerp = pointA + angle.RotateVec(halfway).Normalized() * hypotenuse;
  66. var pointBPerp = pointA + (-angle).RotateVec(halfway).Normalized() * hypotenuse;
  67. var perpLine = pointBPerp - pointAPerp;
  68. var perpHalfway = perpLine.Length() / 2f;
  69. var splinePoint = (pointAPerp + perpLine.Normalized() * random.NextFloat(-args.MaxRatio, args.MaxRatio) * perpHalfway).Floored();
  70. // We essentially take (A, B) and turn it into (A, C) & (C, B)
  71. pairs[i] = (pointA, splinePoint);
  72. pairs.Insert(i + 1, (splinePoint, pointB));
  73. i+= 2;
  74. }
  75. }
  76. var spline = new ValueList<Vector2i>(pairs.Count - 1)
  77. {
  78. start
  79. };
  80. foreach (var pair in pairs)
  81. {
  82. spline.Add(pair.End);
  83. }
  84. // Now we need to pathfind between each node on the spline.
  85. // TODO: Add rotation version or straight-line version for pathfinder config
  86. // Move the worm pathfinder to here I think.
  87. var cameFrom = new Dictionary<Vector2i, Vector2i>();
  88. // TODO: Need to get rid of the branch bullshit.
  89. var points = new List<Vector2i>();
  90. for (var i = 0; i < spline.Count - 1; i++)
  91. {
  92. var point = spline[i];
  93. var target = spline[i + 1];
  94. points.Add(point);
  95. var aStarArgs = args.Args with { Start = point, End = target };
  96. var aStarResult = GetPath(aStarArgs);
  97. if (aStarResult == SimplePathResult.NoPath)
  98. return SplinePathResult.NoPath;
  99. path.AddRange(aStarResult.Path[0..]);
  100. foreach (var a in aStarResult.CameFrom)
  101. {
  102. cameFrom[a.Key] = a.Value;
  103. }
  104. }
  105. points.Add(spline[^1]);
  106. var simple = SimplifyPath(new SimplifyPathArgs()
  107. {
  108. Start = args.Args.Start,
  109. End = args.Args.End,
  110. Path = path,
  111. });
  112. return new SplinePathResult()
  113. {
  114. Path = simple,
  115. CameFrom = cameFrom,
  116. Points = points,
  117. };
  118. }
  119. /// <summary>
  120. /// Does a simpler pathfinder over the nodes to prune unnecessary branches.
  121. /// </summary>
  122. public List<Vector2i> SimplifyPath(SimplifyPathArgs args)
  123. {
  124. var nodes = new HashSet<Vector2i>(args.Path);
  125. var result = GetBreadthPath(new BreadthPathArgs()
  126. {
  127. Start = args.Start,
  128. Ends = new List<Vector2i>()
  129. {
  130. args.End,
  131. },
  132. TileCost = node =>
  133. {
  134. if (!nodes.Contains(node))
  135. return 0f;
  136. return 1f;
  137. }
  138. });
  139. return result.Path;
  140. }
  141. }