| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- using Robust.Shared.Collections;
- using Robust.Shared.Random;
- namespace Content.Server.NPC.Pathfinding;
- public sealed partial class PathfindingSystem
- {
- public record struct SimplifyPathArgs
- {
- public Vector2i Start;
- public Vector2i End;
- public List<Vector2i> Path;
- }
- public record struct SplinePathResult()
- {
- public static SplinePathResult NoPath = new();
- public List<Vector2i> Points = new();
- public List<Vector2i> Path = new();
- public Dictionary<Vector2i, Vector2i>? CameFrom;
- }
- public record struct SplinePathArgs(SimplePathArgs Args)
- {
- public SimplePathArgs Args = Args;
- public float MaxRatio = 0.25f;
- /// <summary>
- /// Minimum distance between subdivisions.
- /// </summary>
- public int Distance = 20;
- }
- /// <summary>
- /// Gets a spline path from start to end.
- /// </summary>
- public SplinePathResult GetSplinePath(SplinePathArgs args, Random random)
- {
- var start = args.Args.Start;
- var end = args.Args.End;
- var path = new List<Vector2i>();
- var pairs = new ValueList<(Vector2i Start, Vector2i End)> { (start, end) };
- var subdivided = true;
- // Sub-divide recursively
- while (subdivided)
- {
- // Sometimes we might inadvertantly get 2 nodes too close together so better to just check each one as it comes up instead.
- var i = 0;
- subdivided = false;
- while (i < pairs.Count)
- {
- var pointA = pairs[i].Start;
- var pointB = pairs[i].End;
- var vector = pointB - pointA;
- var halfway = vector / 2f;
- // Finding the point
- var adj = halfway.Length();
- // Should we even subdivide.
- if (adj <= args.Distance)
- {
- // Just check the next entry no double skip.
- i++;
- continue;
- }
- subdivided = true;
- var opposite = args.MaxRatio * adj;
- var hypotenuse = MathF.Sqrt(MathF.Pow(adj, 2) + MathF.Pow(opposite, 2));
- // Okay so essentially we have 2 points and no poly
- // We add 2 other points to form a diamond and want some point halfway between randomly offset.
- var angle = new Angle(MathF.Atan(opposite / adj));
- var pointAPerp = pointA + angle.RotateVec(halfway).Normalized() * hypotenuse;
- var pointBPerp = pointA + (-angle).RotateVec(halfway).Normalized() * hypotenuse;
- var perpLine = pointBPerp - pointAPerp;
- var perpHalfway = perpLine.Length() / 2f;
- var splinePoint = (pointAPerp + perpLine.Normalized() * random.NextFloat(-args.MaxRatio, args.MaxRatio) * perpHalfway).Floored();
- // We essentially take (A, B) and turn it into (A, C) & (C, B)
- pairs[i] = (pointA, splinePoint);
- pairs.Insert(i + 1, (splinePoint, pointB));
- i+= 2;
- }
- }
- var spline = new ValueList<Vector2i>(pairs.Count - 1)
- {
- start
- };
- foreach (var pair in pairs)
- {
- spline.Add(pair.End);
- }
- // Now we need to pathfind between each node on the spline.
- // TODO: Add rotation version or straight-line version for pathfinder config
- // Move the worm pathfinder to here I think.
- var cameFrom = new Dictionary<Vector2i, Vector2i>();
- // TODO: Need to get rid of the branch bullshit.
- var points = new List<Vector2i>();
- for (var i = 0; i < spline.Count - 1; i++)
- {
- var point = spline[i];
- var target = spline[i + 1];
- points.Add(point);
- var aStarArgs = args.Args with { Start = point, End = target };
- var aStarResult = GetPath(aStarArgs);
- if (aStarResult == SimplePathResult.NoPath)
- return SplinePathResult.NoPath;
- path.AddRange(aStarResult.Path[0..]);
- foreach (var a in aStarResult.CameFrom)
- {
- cameFrom[a.Key] = a.Value;
- }
- }
- points.Add(spline[^1]);
- var simple = SimplifyPath(new SimplifyPathArgs()
- {
- Start = args.Args.Start,
- End = args.Args.End,
- Path = path,
- });
- return new SplinePathResult()
- {
- Path = simple,
- CameFrom = cameFrom,
- Points = points,
- };
- }
- /// <summary>
- /// Does a simpler pathfinder over the nodes to prune unnecessary branches.
- /// </summary>
- public List<Vector2i> SimplifyPath(SimplifyPathArgs args)
- {
- var nodes = new HashSet<Vector2i>(args.Path);
- var result = GetBreadthPath(new BreadthPathArgs()
- {
- Start = args.Start,
- Ends = new List<Vector2i>()
- {
- args.End,
- },
- TileCost = node =>
- {
- if (!nodes.Contains(node))
- return 0f;
- return 1f;
- }
- });
- return result.Path;
- }
- }
|