路径查找算法应用之A*算法

环境:Visual Studio 2017 + .Net Framework 4.5

应用场景:在画板上查找起始点和目标点之间的最短最直路径,最后画出连接两个点之间的折线。

算法简介:A*算法是一种性能较高的最优路径搜索算法,由Stanford Research Institute(now SRI International)的Peter Hart,Nils Nilsson和Bertram Raphael于1968年发表。A*算法可看做是对Dijkstra算法的扩展和优化,其性能一般情况下比Dijkstra算法快得多。在本文的应用场景中,(根据测试)通常比Dijkstra算法快三倍以上,甚至可能比Dijkstra算法快十几倍甚至几十倍。

A*算法的应用范围也比较广泛,如机器人行走路径规划,游戏中的NPC移动计算等。

更详细的算法说明请参考维基百科A* search algorithm

 

实现思想:

1,通过Locator把起始点坐标和目标点坐标对齐到步长(step,默认为20,)的整数倍。这样,起始点和目标点就成了原来的起始点目标点的近似点。

2,把包含起始点和目标点的障碍物(如图中所示,为矩形框)排除掉,不然折线遇到障碍物无法通过。

下图中的矩形框的虚边为避障区域,为了防止折线和障碍物碰撞。

3,把起始点添加到待遍历点的集合中(本文中为SortedList<Vertex>)。

4,从待遍历点的集合中取出第一个点(当前的最优点),遍历其东、南、西、北四个方向的相邻节点。东西两个方向和当前点的Y坐标相同,南北两个方向和当前点的X坐标相同。

相邻点距当前点的距离为step参数设定的步长。step越大,搜索速度越快,然而,可能导致折线无法通过间距较小的障碍物。

如果某个方向的相邻点不存在,则创建新的相邻点(如果相邻点不在障碍物内部的话);同时,设置新创建点的四个相邻点(也许新创建点的相邻点已经被创建了)。

把新创建的相邻点的Visited属性设置为false(当前实现中,Visited属性默认为false),然后对新创建点的所有相邻点排序,取最优点,设置为新创建点的前一个点(调用SetPrev方法)。

再把新创建的点添加到待遍历点的集合中(本文中为SortedList<Vertex>)。

当遍历完当前点的四个方向后,把当前点的Visited属性设置为true,并从带遍历点的集合中移除。

说明:当前算法的实现中仅考虑总距离(从起始点到当前点的距离加上猜测距离,起始点距离为0)、猜测距离(从当前点到目标点的距离,为从当前点到目标点的折线长度)和拐点个数(X或Y坐标变化时拐点个数加1)。

5,递归第4步。要么找到和目标点坐标相同的点(即,找到了目标点),要么待遍历点的集合为空(即,无法找到通往目标点的路径)。

6,当找到通往目标点的路径之后,通过Straightener(调直器)调直路径,减少拐点。

7,处理起始点和目标点。用原来的起始点和目标点替换坐标对齐到step整数倍的起始点和目标点,并调直其相邻拐点的X坐标或Y坐标。

8,返回最终路径。

 

如下两个图所示

第一张图为A*算法查找出来的最优路径(不一定是最短路径,依赖于算法的实现)

第二张图为调直后的最直路径(拐点最少)

 

 

代码

由于工程太大,仅上传必要的代码文件。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Drawing;
  4 
  5 namespace Pathfinding
  6 {
  7     /// <summary>
  8     /// A*算法
  9     /// </summary>
 10     public class AStarAlgorithm
 11     {
 12         private Vertex m_goal;
 13         private Locator m_locator;
 14         private SortedList<Vertex> m_openSet;
 15         private Orientation m_orientation;
 16         private Vertex m_start;
 17         /// <summary>
 18         /// 查找最优路径
 19         /// </summary>
 20         /// <param name="canvas">画布</param>
 21         /// <param name="obstacles">障碍物</param>
 22         /// <param name="step">步长</param>
 23         /// <param name="voDistance">避障距离</param>
 24         /// <param name="initOrient">第一层查找的方向</param>
 25         /// <param name="start">起始点</param>
 26         /// <param name="goal">目标点</param>
 27         /// <returns></returns>
 28         public Point[] Find(Rectangle canvas, List<Rectangle> obstacles, int step, int voDistance, Orientation initOrient, Point start, Point goal)
 29         {
 30             if (start == goal)
 31                 return null;
 32 
 33             if (start.GetDistanceTo(goal) < step)
 34                 return this.ProcessShortPath(start, goal);
 35 
 36             this.Init(canvas, obstacles, step, voDistance, initOrient, start, goal);
 37             this.AddIntoOpenSet(this.m_start);
 38 
 39             Vertex optimal = null;
 40             while (this.m_openSet.Count > 0)
 41             {
 42                 optimal = this.GetOptimalVertex();
 43 
 44                 if (this.IsGoal(optimal))
 45                 {
 46                     this.WalkTarget();
 47                     var path = Straightener.Straighten(this.m_locator, this.m_goal.Lines);
 48                     this.ProcessEndpoint(start, 0, path);
 49                     this.ProcessEndpoint(goal, path.Length - 1, path);
 50 
 51                     return path;
 52                 }
 53 
 54                 this.Walk(optimal);
 55             }
 56 
 57             return null;
 58         }
 59 
 60         /// <summary>
 61         /// 添加待遍历的点
 62         /// </summary>
 63         /// <param name="vertex"></param>
 64         private void AddIntoOpenSet(Vertex vertex)
 65         {
 66             if (!vertex.IsVisited)
 67                 this.m_openSet.Add(vertex);
 68         }
 69 
 70         /// <summary>
 71         /// 获取最优点
 72         /// </summary>
 73         /// <returns></returns>
 74         private Vertex GetOptimalVertex()
 75         {
 76             var cheapest = this.m_openSet.TakeFirst();
 77             cheapest.IsCurrent = true;
 78 
 79             return cheapest;
 80         }
 81 
 82         /// <summary>
 83         /// 估算到目标点的距离
 84         /// </summary>
 85         /// <param name="vertex"></param>
 86         /// <returns></returns>
 87         private int GuessDistanceToGoal(Vertex vertex)
 88         {
 89             return Math.Abs(vertex.X - this.m_goal.X) + Math.Abs(vertex.Y - this.m_goal.Y);
 90         }
 91 
 92         /// <summary>
 93         /// 初始化数据
 94         /// </summary>
 95         /// <param name="canvas"></param>
 96         /// <param name="obstacles"></param>
 97         /// <param name="step"></param>
 98         /// <param name="voDistance"></param>
 99         /// <param name="initOrient"></param>
100         /// <param name="start"></param>
101         /// <param name="goal"></param>
102         private void Init(Rectangle canvas, List<Rectangle> obstacles, int step, int voDistance, Orientation initOrient, Point start, Point goal)
103         {
104             this.m_locator = new Locator(canvas, obstacles, step, voDistance);
105 
106             this.m_locator.AlignPoint(ref start);
107             this.m_locator.AlignPoint(ref goal);
108             this.m_locator.ExcludeObstacles(start);
109             this.m_locator.ExcludeObstacles(goal);
110 
111             this.m_start = new Vertex()
112             {
113                 Location = start
114             };
115             this.m_goal = new Vertex()
116             {
117                 Location = goal
118             };
119             this.m_openSet = new SortedList<Vertex>();
120             this.m_start.GuessDistance = this.GuessDistanceToGoal(this.m_start);
121             this.m_orientation = initOrient;
122         }
123 
124         /// <summary>
125         /// 是否是目标点
126         /// </summary>
127         /// <param name="vertex"></param>
128         /// <returns></returns>
129         private bool IsGoal(Vertex vertex)
130         {
131             if (vertex.Location == this.m_goal.Location)
132             {
133                 this.m_goal = vertex;
134                 return true;
135             }
136 
137             return false;
138         }
139 
140         /// <summary>
141         /// 处理端点(起始点或目标点)
142         /// </summary>
143         /// <param name="endpoint"></param>
144         /// <param name="idx"></param>
145         /// <param name="path"></param>
146         private void ProcessEndpoint(Point endpoint, int idx, Point[] path)
147         {
148             Point approximatePoint = path[idx];
149             if (0 == idx)
150             {
151                 path[0] = endpoint;
152                 idx += 1;
153             }
154             else
155             {
156                 path[idx] = endpoint;
157                 idx -= 1;
158             }
159 
160             if (approximatePoint.X == path[idx].X)
161                 path[idx].X = endpoint.X;
162             else
163                 path[idx].Y = endpoint.Y;
164         }
165 
166         /// <summary>
167         /// 处理短路径
168         /// </summary>
169         /// <param name="start"></param>
170         /// <param name="goal"></param>
171         /// <returns></returns>
172         private Point[] ProcessShortPath(Point start, Point goal)
173         {
174             var dx = Math.Abs(goal.X - start.X);
175             var dy = Math.Abs(goal.Y - start.Y);
176             if (dx >= dy)
177                 return new Point[] { start, new Point(goal.X, start.Y), goal };
178             else
179                 return new Point[] { start, new Point(start.X, goal.Y), goal };
180         }
181 
182         /// <summary>
183         /// 设置前一个点
184         /// </summary>
185         /// <param name="vertex"></param>
186         private void SetPrev(Vertex vertex)
187         {
188             var neighbors = vertex.Neighbors;
189             neighbors.Sort();
190             vertex.SetPrev(neighbors[0]);
191             vertex.GuessDistance = this.GuessDistanceToGoal(vertex);
192         }
193 
194 
195         #region Traverse Neighbors
196 
197         /// <summary>
198         /// 创建东边的相邻点
199         /// </summary>
200         /// <param name="vertex"></param>
201         private void CreateEastNeighbor(Vertex vertex)
202         {
203             var location = new Point(vertex.X + this.m_locator.Step, vertex.Y);
204             if (this.m_locator.AlignPoint(ref location)
205                 && Orientation.East == vertex.Location.GetOrientation(location))
206             {
207                 var neighbor = new Vertex()
208                 {
209                     Location = location
210                 };
211 
212                 //213                 //     |
214                 // ◐---●---○
215                 //     |
216                 //
217                 vertex.EastNeighbor = neighbor;
218                 //     ◐---◐
219                 //     |   |
220                 // ◐---●---○
221                 //     |
222                 //
223                 neighbor.NorthNeighbor = vertex.NorthNeighbor?.EastNeighbor;
224                 //225                 //     |
226                 // ◐---●---○
227                 //     |   |
228                 //     ◐---◐
229                 neighbor.SouthNeighbor = vertex.SouthNeighbor?.EastNeighbor;
230 
231                 this.SetPrev(neighbor);
232                 this.AddIntoOpenSet(neighbor);
233             }
234             else
235                 vertex.CouldWalkEast = false;
236         }
237 
238         /// <summary>
239         /// 创建北边的相邻点
240         /// </summary>
241         /// <param name="vertex"></param>
242         private void CreateNorthNeighbor(Vertex vertex)
243         {
244             var location = new Point(vertex.X, vertex.Y - this.m_locator.Step);
245             if (this.m_locator.AlignPoint(ref location)
246                 && Orientation.North == vertex.Location.GetOrientation(location))
247             {
248                 var neighbor = new Vertex()
249                 {
250                     Location = location
251                 };
252 
253                 //254                 //     |
255                 // ◐---●---◐
256                 //     |
257                 //
258                 vertex.NorthNeighbor = neighbor;
259                 //     ○---◐
260                 //     |   |
261                 // ◐---●---◐
262                 //     |
263                 //
264                 neighbor.EastNeighbor = vertex.EastNeighbor?.NorthNeighbor;
265                 // ◐---○
266                 // |   |
267                 // ◐---●---◐
268                 //     |
269                 //
270                 neighbor.WestNeighbor = vertex.WestNeighbor?.NorthNeighbor;
271 
272                 this.SetPrev(neighbor);
273                 this.AddIntoOpenSet(neighbor);
274             }
275             else
276                 vertex.CouldWalkNorth = false;
277         }
278 
279         /// <summary>
280         /// 创建南边的相邻点
281         /// </summary>
282         /// <param name="vertex"></param>
283         private void CreateSouthNeighbor(Vertex vertex)
284         {
285             var location = new Point(vertex.X, vertex.Y + this.m_locator.Step);
286             if (this.m_locator.AlignPoint(ref location)
287                 && Orientation.South == vertex.Location.GetOrientation(location))
288             {
289                 var neighbor = new Vertex()
290                 {
291                     Location = location
292                 };
293 
294                 //295                 //     |
296                 // ◐---●---◐
297                 //     |
298                 //
299                 vertex.SouthNeighbor = neighbor;
300                 //301                 //     |
302                 // ◐---●---◐
303                 //     |   |
304                 //     ○---◐
305                 neighbor.EastNeighbor = vertex.EastNeighbor?.SouthNeighbor;
306                 //307                 //     |
308                 // ◐---●---◐
309                 // |   |
310                 // ◐---○
311                 neighbor.WestNeighbor = vertex.WestNeighbor?.SouthNeighbor;
312 
313                 this.SetPrev(neighbor);
314                 this.AddIntoOpenSet(neighbor);
315             }
316             else
317                 vertex.CouldWalkSouth = false;
318         }
319 
320         /// <summary>
321         /// 创建西边的相邻点
322         /// </summary>
323         /// <param name="vertex"></param>
324         private void CreateWestNeighbor(Vertex vertex)
325         {
326             var location = new Point(vertex.X - this.m_locator.Step, vertex.Y);
327             if (this.m_locator.AlignPoint(ref location)
328                 && Orientation.West == vertex.Location.GetOrientation(location))
329             {
330                 var neighbor = new Vertex()
331                 {
332                     Location = location
333                 };
334 
335                 //336                 //     |
337                 // ○---●---◐
338                 //     |
339                 //
340                 vertex.WestNeighbor = neighbor;
341                 //342                 //     |
343                 // ○---●---◐
344                 // |   |
345                 // ◐---◐
346                 neighbor.SouthNeighbor = vertex.SouthNeighbor?.WestNeighbor;
347                 // ◐---◐
348                 // |   |
349                 // ○---●---◐
350                 //     |
351                 //
352                 neighbor.NorthNeighbor = vertex.NorthNeighbor?.WestNeighbor;
353 
354                 this.SetPrev(neighbor);
355                 this.AddIntoOpenSet(neighbor);
356             }
357             else
358                 vertex.CouldWalkWest = false;
359         }
360 
361         /// <summary>
362         /// <para>遍历四个方位的相邻点:东、南、西、北</para>
363         /// <para>●(实心圆)表示访问过的点</para>
364         /// <para>◐(半实心圆)表示可能访问过的点</para>
365         /// <para>○(空心圆)表示未访问过的点</para>
366         /// </summary>
367         /// <param name="vertex"></param>
368         private void Walk(Vertex vertex)
369         {
370             //371             //     |
372             // ◐---●---◐
373             //     |
374             //
375 
376             var count = 0;
377             while (count++ < 4)
378             {
379                 switch (this.m_orientation)
380                 {
381                     case Orientation.East:
382                         this.WalkEast(vertex);
383                         this.m_orientation = Orientation.South;
384                         break;
385                     case Orientation.South:
386                         this.WalkSouth(vertex);
387                         this.m_orientation = Orientation.West;
388                         break;
389                     case Orientation.West:
390                         this.WalkWest(vertex);
391                         this.m_orientation = Orientation.North;
392                         break;
393                     case Orientation.North:
394                         this.WalkNorth(vertex);
395                         this.m_orientation = Orientation.East;
396                         break;
397                     default:
398                         this.m_orientation = Orientation.East;
399                         break;
400                 }
401             }
402 
403             vertex.IsVisited = true;
404             vertex.IsCurrent = false;
405         }
406 
407         /// <summary>
408         /// 遍历东边的相邻点
409         /// </summary>
410         /// <param name="vertex"></param>
411         private void WalkEast(Vertex vertex)
412         {
413             if (vertex.CouldWalkEast && vertex.EastNeighbor is null)
414                 this.CreateEastNeighbor(vertex);
415         }
416 
417         /// <summary>
418         /// 遍历北边的相邻点
419         /// </summary>
420         /// <param name="vertex"></param>
421         private void WalkNorth(Vertex vertex)
422         {
423             if (vertex.CouldWalkNorth && vertex.NorthNeighbor is null)
424                 this.CreateNorthNeighbor(vertex);
425         }
426 
427         /// <summary>
428         /// 遍历南边的相邻点
429         /// </summary>
430         /// <param name="vertex"></param>
431         private void WalkSouth(Vertex vertex)
432         {
433             if (vertex.CouldWalkSouth && vertex.SouthNeighbor is null)
434                 this.CreateSouthNeighbor(vertex);
435         }
436 
437         /// <summary>
438         /// 遍历目标点
439         /// </summary>
440         private void WalkTarget()
441         {
442             // 遍历目标点及其相邻点
443             this.Walk(this.m_goal);
444 
445             if (null != this.m_goal.EastNeighbor)
446                 this.Walk(this.m_goal.EastNeighbor);
447             if (null != this.m_goal.SouthNeighbor)
448                 this.Walk(this.m_goal.SouthNeighbor);
449             if (null != this.m_goal.WestNeighbor)
450                 this.Walk(this.m_goal.WestNeighbor);
451             if (null != this.m_goal.NorthNeighbor)
452                 this.Walk(this.m_goal.NorthNeighbor);
453 
454             this.SetPrev(this.m_goal);
455         }
456 
457         /// <summary>
458         /// 遍历西边的相邻点
459         /// </summary>
460         /// <param name="vertex"></param>
461         private void WalkWest(Vertex vertex)
462         {
463             if (vertex.CouldWalkWest && vertex.WestNeighbor is null)
464                 this.CreateWestNeighbor(vertex);
465         }
466 
467         #endregion Traverse Neighbors
468     }
469 }
AStarAlgorithm
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace Pathfinding
{
    /// <summary>
    /// 定位器(用于避障,查找相邻点或者对齐坐标等)
    /// </summary>
    public class Locator
    {
        /// <summary>
        /// 画布
        /// </summary>
        private readonly Rectangle m_canvas;

        /// <summary>
        /// 障碍物
        /// </summary>
        private readonly List<Rectangle> m_obstacles;

        /// <summary>
        /// 步长
        /// </summary>
        private readonly int m_step;

        /// <summary>
        /// 避障距离
        /// </summary>
        private readonly int m_voDistance;

        public Locator(Rectangle canvas, List<Rectangle> obstacles, int step = 20, int voDistance = 10)
        {
            this.m_canvas = canvas;
            if (step < 20)
                step = 20;
            this.m_step = (step >= 20) ? step : 20;
            this.m_voDistance = (voDistance >= 10) ? voDistance : 10;
            this.m_obstacles = this.BuildObstacles(obstacles);
        }

        /// <summary>
        /// 画板
        /// </summary>
        public Rectangle Canvas => this.m_canvas;

        /// <summary>
        /// 步长
        /// </summary>
        public int Step => this.m_step;

        /// <summary>
        /// 避障距离
        /// </summary>
        public int VODistance => this.m_voDistance;

        /// <summary>
        /// 对齐坐标(把“点”的坐标值对齐到Step的整数倍)
        /// </summary>
        /// <param name="point"></param>
        public Point AlignPoint(Point point)
        {
            return new Point(this.AlignCoord(point.X, 1), this.AlignCoord(point.Y, 1));
        }

        /// <summary>
        /// <para>对齐坐标(把“点”的坐标值对齐到Step的整数倍,同时校验“点”是否在画布内或是否和障碍物冲突)</para>
        /// <para>如果对齐前或对齐后的“点”坐标不在画布内或者和障碍物冲突,则返回false;否则,返回true。</para>
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool AlignPoint(ref Point point)
        {
            if (!this.m_canvas.Contains(point))
                return false;

            point = this.AlignPoint(point);

            if (!this.m_canvas.Contains(point))
                return false;

            return !this.InObstacle(point);
        }

        /// <summary>
        /// 排除包含“点”的障碍物
        /// </summary>
        /// <param name="point"></param>
        public void ExcludeObstacles(Point point)
        {
            this.m_obstacles.RemoveAll(o => o.Contains(point));
        }

        /// <summary>
        /// 判断点是否在障碍物内
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool InObstacle(Point point)
        {
            return this.m_obstacles.Exists(obst => obst.Contains(point));
        }

        /// <summary>
        /// <para>判断水平线段或垂直线段(ab)是否和障碍物相交</para>
        /// <para>a、b的顺序和结果无关</para>
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public bool IntersectWithObstacle(Point a, Point b)
        {
            if (a.X == b.X)
                return this.IntersectVerticallyWithObstacle(a, b);
            else // a.Y == b.Y
                return this.IntersectHorizontallyWithObstacle(a, b);
        }

        /// <summary>
        /// 判断水平线段(ab,其中a.X &#8804; b.X)是否和障碍物相交
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public bool IntersectHorizontallyWithObstacle(Point a, Point b)
        {
            if (a.X > b.X)
            {
                var t = a;
                a = b;
                b = t;
            }

            return this.m_obstacles.Exists(obst =>
                (obst.Top <= a.Y && a.Y <= obst.Bottom && ((a.X <= obst.Left && obst.Left <= b.X) || (a.X <= obst.Right && obst.Right <= b.X)))
                || obst.Contains(a)
                || obst.Contains(b));
        }

        /// <summary>
        /// 判断垂直线段(ab,其中a.Y &#8804; b.Y)是否和障碍物相交
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public bool IntersectVerticallyWithObstacle(Point a, Point b)
        {
            if (a.Y > b.Y)
            {
                var t = a;
                a = b;
                b = t;
            }

            return this.m_obstacles.Exists(obst =>
                (obst.Left <= a.X && a.X <= obst.Right && ((a.Y <= obst.Top && obst.Top <= b.Y) || (a.Y <= obst.Bottom && obst.Bottom <= b.Y)))
                || obst.Contains(a)
                || obst.Contains(b));
        }

        /// <summary>
        /// 对齐坐标的值
        /// </summary>
        /// <param name="val"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        private int AlignCoord(int val, int direction)
        {
            int md = val % this.m_step;
            if (0 == md)
                return val;
            else if (md <= this.m_step / 2)
                return val - md;
            else
                return val - md + (direction * this.m_step);
        }

        /// <summary>
        /// 构造障碍物(用于调试)
        /// </summary>
        /// <param name="obstacles"></param>
        /// <returns></returns>
        private List<Rectangle> BuildDebugObstacles(List<Rectangle> obstacles)
        {
            if (obstacles is null || obstacles.Count <= 0)
                return new List<Rectangle>();
            else
                return obstacles.Select(o => new Rectangle(o.X - this.m_voDistance,
                                                           o.Y - this.m_voDistance,
                                                           o.Width + this.m_voDistance * 2,
                                                           o.Height + m_voDistance * 2)).ToList();
        }

        /// <summary>
        /// 构造障碍物
        /// </summary>
        /// <param name="obstacles"></param>
        /// <returns></returns>
        private List<Rectangle> BuildObstacles(List<Rectangle> obstacles)
        {
            if (obstacles is null || obstacles.Count <= 0)
                return new List<Rectangle>();
            else
                return obstacles.Select(o => new Rectangle(o.X - this.m_voDistance + 1,
                                                           o.Y - this.m_voDistance + 1,
                                                           o.Width + this.m_voDistance * 2 - 2,
                                                           o.Height + m_voDistance * 2 - 2)).ToList();
        }
    }
}
Locator
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace Pathfinding
{
    /// <summary>
    /// 路径调制器
    /// </summary>
    public class Straightener
    {
        /// <summary>
        /// 满足调直的前提条件(路径最少包含4个点)
        /// </summary>
        private const int MIN_COUNT_POINTS = 4;
        /// <summary>
        /// 定位器
        /// </summary>
        private Locator m_locator;
        /// <summary>
        /// 原始路径
        /// </summary>
        private LinkedList<Point> m_originalPath;
        /// <summary>
        /// 调直后的路径
        /// </summary>
        private LinkedList<Point> m_straightenedPath;

        private Straightener()
        {
            this.Reset();
        }

        /// <summary>
        /// 调直路径,减少拐点(参数为空或小于4个点不做任何处理)
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static Point[] Straighten(Locator locator, Point[] path)
        {
            if (locator is null || path is null || path.Length < MIN_COUNT_POINTS)
                return path;

            var worker = new Straightener();
            worker.m_locator = locator;
            worker.m_originalPath = new LinkedList<Point>(path);
            worker.Straighten();

            return worker.m_straightenedPath.ToArray();
        }

        /// <summary>
        /// 创建假设的拐点
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static Point CreateHypotheticalInflection(LinkedListNode<Point> node)
        {
            if (node.Value.X == node.Next.Value.X)
                return new Point(node.Next.Next.Value.X, node.Value.Y);
            else
                return new Point(node.Value.X, node.Next.Next.Value.Y);
        }

        /// <summary>
        /// 计算线段(abcd)上拐点的个数
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <param name="d"></param>
        /// <returns></returns>
        private static int GetCountInflections(Point a, Point b, Point c, Point d)
        {
            var inflections = 0;
            if (c.X != a.X && c.Y != a.Y)
                inflections++;
            if (d.X != b.X && d.Y != b.Y)
                inflections++;

            return inflections;
        }

        private static int GetDistance(Point a, Point b)
        {
            if (a.X == b.X)
                return Math.Abs(a.Y - b.Y);
            else
                return Math.Abs(a.X - b.X);
        }
        /// <summary>
        /// 添加拐点
        /// </summary>
        /// <param name="inflection"></param>
        private void AddInflection(Point inflection)
        {
            if (null != this.m_straightenedPath.Last
                && this.m_straightenedPath.Last.Value == inflection)
                return;

            var last = this.m_straightenedPath.AddLast(inflection);
            if (null != last.Previous?.Previous
                && (last.Value.X == last.Previous.Previous.Value.X
                    || last.Value.Y == last.Previous.Previous.Value.Y))
                this.m_straightenedPath.Remove(last.Previous);
        }

        private void DoStraighten()
        {
            this.Reset();

            var current = this.m_originalPath.First;
            while (null != current.Next?.Next)
            {
                this.AddInflection(current.Value);

                this.RemoveRedundantInflections(current);
                if (current.Next?.Next is null)
                    break;

                var inflection = CreateHypotheticalInflection(current);
                if (!this.IntersectWithObstacle(current.Value, inflection, current.Next.Next.Value))
                {
                    var success = false;

                    if (null != current.Previous)
                    {
                        var i1 = GetCountInflections(current.Previous.Value, current.Value, current.Next.Value, current.Next.Next.Value);
                        var i2 = GetCountInflections(current.Previous.Value, current.Value, inflection, current.Next.Next.Value);
                        if (i2 < i1)
                        {
                            current.Next.Value = inflection;
                            success = true;
                        }
                    }
                    else if (null != current.Next?.Next?.Next)
                    {
                        var i3 = GetCountInflections(current.Value, current.Next.Value, current.Next.Next.Value, current.Next.Next.Next.Value);
                        var i4 = GetCountInflections(current.Value, inflection, current.Next.Next.Value, current.Next.Next.Next.Value);
                        if (i4 < i3)
                        {
                            current.Next.Value = inflection;
                            success = true;
                        }
                    }

                    if (success)
                    {
                        this.RemoveRedundantInflections(current.Next);
                        if (current.Next?.Next is null)
                            break;
                    }
                }

                this.RemoveTurnBackInflections(current);

                current = current.Next;
            }

            this.AddInflection(this.m_originalPath.Last.Previous.Value);
            this.AddInflection(this.m_originalPath.Last.Value);
        }

        private int GetDistance()
        {
            var dist = 0;
            var current = this.m_straightenedPath.First;
            do
            {
                if (null != current.Next)
                {
                    dist += GetDistance(current.Value, current.Next.Value);
                    current = current.Next;
                }
                else
                    break;

            } while (true);

            return dist;
        }

        /// <summary>
        /// 判断线段(abc)是否和障碍物相交
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        private bool IntersectWithObstacle(Point a, Point b, Point c)
        {
            return this.m_locator.IntersectWithObstacle(a, b)
                || this.m_locator.IntersectWithObstacle(b, c);
        }

        /// <summary>
        /// 删除冗余拐点
        /// </summary>
        /// <param name="node"></param>
        private void RemoveRedundantInflections(LinkedListNode<Point> node)
        {
            while (true)
            {
                if (node.Next?.Next is null)
                    break;

                if (node.Value.X == node.Next.Next.Value.X
                    || node.Value.Y == node.Next.Next.Value.Y)
                    this.m_originalPath.Remove(node.Next);
                else
                    break;
            }
        }

        /// <summary>
        /// 删除回转拐点
        /// </summary>
        /// <param name="node"></param>
        private void RemoveTurnBackInflections(LinkedListNode<Point> node)
        {
            if (node.Next?.Next?.Next is null)
                return;

            var point = node.Value;
            var nPoint = node.Next.Value;
            var nnPoint = node.Next.Next.Value;
            var nnnPoint = node.Next.Next.Next.Value;

            // ●为已知拐点;○为假设拐点
            // 消除如下形式的拐点
            //// |
            // ○---●
            // |   |
            // ●---●
            if (point.X == nPoint.X
                && nPoint.Y == nnPoint.Y
                && nnPoint.X == nnnPoint.X)
            {
                var dy1 = point.Y - nPoint.Y;
                var dy2 = nnnPoint.Y - nnPoint.Y;
                var p1 = new Point(nnnPoint.X, point.Y);
                if (Math.Abs(dy2) >= Math.Abs(dy1)
                    && Math.Abs(dy1) / dy1 == Math.Abs(dy2) / dy2
                    && !this.m_locator.IntersectHorizontallyWithObstacle(node.Value, p1))
                {
                    this.m_originalPath.Remove(node.Next);
                    this.m_originalPath.Remove(node.Next);
                    this.m_originalPath.AddAfter(node, p1);
                }
            }
            // ●为已知拐点;○为假设拐点
            // 消除如下形式的拐点
            // ●---○---●
            // |   |
            // ●---●
            else if (point.Y == nPoint.Y
                && nPoint.X == nnPoint.X
                && nnPoint.Y == nnnPoint.Y)
            {
                var dx1 = point.X - nPoint.X;
                var dx2 = nnnPoint.X - nnPoint.X;
                var p2 = new Point(point.X, nnnPoint.Y);
                if (Math.Abs(dx2) >= Math.Abs(dx1)
                    && Math.Abs(dx1) / dx1 == Math.Abs(dx2) / dx2
                    && !this.m_locator.IntersectVerticallyWithObstacle(node.Value, p2))
                {
                    this.m_originalPath.Remove(node.Next);
                    this.m_originalPath.Remove(node.Next);
                    this.m_originalPath.AddAfter(node, p2);
                }
            }
        }

        private void Reset()
        {
            this.m_straightenedPath = new LinkedList<Point>();
        }

        private void Straighten()
        {
            int prevDistance = 0;
            int prevInflections = 0;
            int distance = 0;
            int inflections = 0;

            while (true)
            {
                this.DoStraighten();
                this.m_originalPath = this.m_straightenedPath;

                distance = this.GetDistance();
                inflections = this.m_originalPath.Count;

                if (distance == prevDistance
                    && inflections == prevInflections)
                    break;

                prevDistance = distance;
                prevInflections = inflections;
            }
        }
    }
}
Straightener
using System;
using System.Collections;
using System.Collections.Generic;

namespace Pathfinding
{
    /// <summary>
    /// <para>有序链表</para>
    /// <para>此类不是线程安全的</para>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SortedList<T> : IEnumerable<T> where T : IComparable<T>
    {
        private int m_count = 0;
        private Node m_first;
        private Node m_last;

        public SortedList()
        {
            // do nothing
        }

        public SortedList(IEnumerable<T> collection)
        {
            this.AddRange(collection);
        }

        /// <summary>
        /// 链表中的元素个数
        /// </summary>
        public int Count => this.m_count;

        /// <summary>
        /// 第一个元素
        /// </summary>
        public T First
        {
            get
            {
                if (null != this.m_first)
                    return this.m_first.Value;
                else
                    return default(T);
            }
        }

        public bool IsEmpty => this.m_count <= 0;

        /// <summary>
        /// 最后一个元素
        /// </summary>
        public T Last
        {
            get
            {
                if (null != this.m_last)
                    return this.m_last.Value;
                else
                    return default(T);
            }
        }

        public void Add(T value)
        {
            var node = new Node(value);
            if (this.IsEmpty)
            {
                this.m_first = node;
                this.m_last = node;
            }
            else if (value.CompareTo(this.m_first.Value) < 0)
            {
                node.Next = this.m_first;
                this.m_first.Prev = node;
                this.m_first = node;
            }
            else if (this.m_last.Value.CompareTo(value) <= 0)
            {
                node.Prev = this.m_last;
                this.m_last.Next = node;
                this.m_last = node;
            }
            else
            {
                Node current = this.m_first;
                do
                {
                    if (value.CompareTo(current.Value) < 0)
                        break;

                    current = current.Next;

                } while (current != null);


                var prev = current.Prev;
                prev.Next = node;
                node.Prev = prev;

                node.Next = current;
                current.Prev = node;
            }

            this.m_count++;
        }

        public void AddRange(IEnumerable<T> collection)
        {
            if (collection is null)
                return;

            foreach (var item in collection)
                this.Add(item);
        }

        /// <summary>
        /// 清除所有元素
        /// </summary>
        public void Clear()
        {
            this.m_first = null;
            this.m_last = null;
            this.m_count = 0;
        }

        /// <summary>
        /// 判断链表是否包含指定的元素
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool Contains(T value)
        {
            if (this.IsEmpty)
                return false;

            var current = this.m_first;
            while (null != current)
            {
                if (value.CompareTo(current.Value) == 0)
                    return true;

                current = current.Next;
            }

            return false;
        }

        public int IndexOf(T value)
        {
            if (this.IsEmpty)
                return -1;

            var current = this.m_first;
            var idx = 0;
            while (null != current)
            {
                if (value.CompareTo(current.Value) == 0)
                    return idx;

                idx++;
                current = current.Next;
            }

            return -1;
        }

        /// <summary>
        /// 获取IEnumerator<T>
        /// </summary>
        /// <returns></returns>
        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        /// <summary>
        /// 删除指定的元素
        /// </summary>
        /// <param name="value"></param>
        public void Remove(T value)
        {
            if (this.IsEmpty)
                return;

            var current = this.m_first;
            while (null != current)
            {
                if (value.CompareTo(current.Value) == 0)
                    break;

                current = current.Next;
            }

            if (null != current)
            {
                var prev = current.Prev;
                var next = current.Next;

                if (null != prev && null != next)
                {
                    prev.Next = next;
                    next.Prev = prev;
                }
                else if (null != prev)
                    this.SetLast(prev);
                else
                    this.SetFirst(next);

                this.m_count--;
            }
        }

        /// <summary>
        /// 返回并删除第一个元素
        /// </summary>
        /// <returns></returns>
        public T TakeFirst()
        {
            if (this.IsEmpty)
                return default(T);

            var value = this.m_first.Value;

            this.SetFirst(this.m_first.Next);
            this.m_count--;

            return value;
        }

        /// <summary>
        /// 返回并删除最后一个元素
        /// </summary>
        /// <returns></returns>
        public T TakeLast()
        {
            if (this.IsEmpty)
                return default(T);

            var value = this.m_last.Value;

            this.SetLast(this.m_last.Prev);
            this.m_count--;

            return value;
        }

        public T[] ToArray()
        {
            if (this.IsEmpty)
                return null;

            var a = new T[this.m_count];
            var current = this.m_first;
            var idx = 0;
            while (null != current)
            {
                a[idx++] = current.Value;
                current = current.Next;
            }

            return a;
        }

        public List<T> ToList()
        {
            if (this.IsEmpty)
                return null;

            var l = new List<T>(this.m_count);
            var current = this.m_first;
            while (null != current)
            {
                l.Add(current.Value);
            }

            return l;
        }

        private void SetFirst(Node first)
        {
            this.m_first = first;
            if (this.m_first is null)
                this.m_last = null;
            else
                this.m_first.Prev = null;
        }

        private void SetLast(Node last)
        {
            this.m_last = last;
            if (this.m_last is null)
                this.m_first = null;
            else
                this.m_last.Next = null;
        }

        /// <summary>
        /// 枚举器
        /// </summary>
        private class Enumerator : IEnumerator<T>
        {
            private readonly SortedList<T> m_list;
            private readonly Node m_prevFirst = new Node(default(T));
            private Node m_current;

            public Enumerator(SortedList<T> list)
            {
                this.m_list = list;
                this.Reset();
            }

            public T Current
            {
                get
                {
                    if (null != this.m_current)
                        return this.m_current.Value;
                    else
                        return default(T);
                }
            }

            object IEnumerator.Current
            {
                get
                {
                    if (null != this.m_current)
                        return this.m_current.Value;
                    else
                        return default(T);
                }
            }

            public void Dispose()
            {
                // do nothing
            }

            public bool MoveNext()
            {
                if (object.ReferenceEquals(this.m_current, this.m_prevFirst))
                    this.m_current = this.m_list.m_first;
                else
                    this.m_current = this.m_current?.Next;

                return null != this.m_current;
            }

            public void Reset()
            {
                this.m_current = this.m_prevFirst;
            }
        }

        /// <summary>
        /// 链表节点
        /// </summary>
        private class Node
        {
            public Node(T data)
            {
                this.Value = data;
            }

            public Node Next { get; set; }

            public Node Prev { get; set; }

            public T Value { get; }

            public override string ToString()
            {
                if (null != Value)
                    return Value.ToString();
                return null;
            }
        }
    }
}
SortedList
namespace Pathfinding
{
    /// <summary>
    /// 方向
    /// </summary>
    public enum Orientation
    {
        /// <summary>
        /// 无方向
        /// </summary>
        None = 0,
        /// <summary>
        ////// </summary>
        East = 0x1,
        /// <summary>
        ////// </summary>
        South = 0x10,
        /// <summary>
        /// 西
        /// </summary>
        West = 0x100,
        /// <summary>
        ////// </summary>
        North = 0x1000,
        /// <summary>
        /// 东西
        /// </summary>
        EastWest = East | West,
        /// <summary>
        /// 南北
        /// </summary>
        NorthSouth = South | North,
        /// <summary>
        /// 东南
        /// </summary>
        SouthEast = East | South,
        /// <summary>
        /// 西南
        /// </summary>
        SouthWest = South | West,
        /// <summary>
        /// 西北
        /// </summary>
        NorthWest = West | North,
        /// <summary>
        /// 东北
        /// </summary>
        NorthEast = North | East,
    }
}
Orientation
namespace Pathfinding
{
    public static class OrientationExtension
    {
        /// <summary>
        /// 是否为东西方向
        /// </summary>
        /// <param name="orient"></param>
        /// <returns></returns>
        public static bool IsEastWest(this Orientation orient)
        {
            return orient == Orientation.East
                || orient == Orientation.West
                || orient == Orientation.EastWest;
        }

        /// <summary>
        /// 是否为南北方向
        /// </summary>
        /// <param name="orient"></param>
        /// <returns></returns>
        public static bool IsNorthSouth(this Orientation orient)
        {
            return orient == Orientation.South
                || orient == Orientation.North
                || orient == Orientation.NorthSouth;
        }

        /// <summary>
        /// <para>把方向转换为EastWest或NorthSouth</para>
        /// <para>如果方向不是东西方向或南北方向,则返回None</para>
        /// </summary>
        /// <param name="orient"></param>
        /// <returns></returns>
        public static Orientation ConvertToEWOrNS(this Orientation orient)
        {
            if (orient.IsEastWest())
                return Orientation.EastWest;
            else if (orient.IsNorthSouth())
                return Orientation.NorthSouth;
            else
                return Orientation.None;
        }
    }
}
OrientationExtension
using System;
using System.Drawing;

namespace Pathfinding
{
    public static class PointExtension
    {
        /// <summary>
        /// <para>获取第二个点相对于第一个点的方位</para>
        /// <para>此方法只判断是否为正南,正北,正东或正西四个方向。</para>
        /// <para>如果两个点坐标一样,则返回Orientation.None。</para>
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns>East、South、West、North</returns>
        public static Orientation GetOrientation(this Point from, Point to)
        {
            if (from.X == to.X)
            {
                if (to.Y > from.Y)
                    return Orientation.South;
                else if (to.Y < from.Y)
                    return Orientation.North;
            }
            else if (from.Y == to.Y)
            {
                if (to.X > from.X)
                    return Orientation.East;
                else if (to.X < from.X)
                    return Orientation.West;
            }

            return Orientation.None;
        }

        /// <summary>
        /// <para>判断两点之间的相对位置:东西方向或南北方向</para>
        /// <para>如果两个点坐标一样或不是东西或南北方向,则返回Orientation.None。</para>
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns>EastWest或NorthSouth</returns>
        public static Orientation GetOrientationEWOrNS(this Point from, Point to)
        {
            if (from.X == to.X && to.Y != from.Y)
            {
                return Orientation.NorthSouth;
            }
            else if (from.Y == to.Y && to.X != from.X)
            {
                return Orientation.EastWest;
            }

            return Orientation.None;
        }

        /// <summary>
        /// 两点的位置是否为东西方向:Y坐标相等,且X坐标不相等
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public static bool IsEastWest(this Point from, Point to)
        {
            return from.Y == to.Y && to.X != from.X;
        }

        /// <summary>
        /// 两点的位置是否为南北方向:X坐标相等,且Y坐标不相等
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public static bool IsNorthSouth(this Point from, Point to)
        {
            return from.X == to.X && to.Y != from.Y;
        }

        /// <summary>
        /// 计算两点之间的距离(仅计算东西和南北方向的距离)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int GetAlignedDistanceTo(this Point a, Point b)
        {
            if (a.IsEastWest(b))
                return Math.Abs(a.X - b.X);
            else
                return Math.Abs(a.Y - b.Y);
        }

        /// <summary>
        /// 计算两点之间的距离
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public static double GetDistanceTo(this Point from, Point to)
        {
            return Math.Sqrt(Math.Pow(from.X - to.X, 2.0d) + Math.Pow(from.Y - to.Y, 2.0d));
        }

        /// <summary>
        /// 判断两个点是否在东西方向或南北方向的同一条直线上
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool InStraightLine(this Point a, Point b)
        {
            return a.X == b.X || a.Y == b.Y;
        }
    }
}
PointExtension

 

posted @ 2022-03-16 08:03  腾鲲  阅读(883)  评论(1编辑  收藏  举报