Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C# 4.0

A Complex - Yet Quite Intelligible - Pathfinding in C#

Rate me:
Please Sign up or sign in to vote.
4.96/5 (41 votes)
30 Aug 2013CPOL21 min read 59.5K   2.5K   88  
A semi-analytic 2D path-finding for large dynamic scenarios.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.Evasion;

namespace YinYang.CodeProject.Projects.SimplePathfinding.Helpers
{
    public class LineRasterizer
    {
        #region | Common |

        protected static Int32 GetDirection(Int32 value, out Boolean positive)
        {
            positive = value > 0;
            return positive ? 1 : -1;
        }

        protected static Int32 GetDirection(Int32 lowValue, Int32 highValue)
        {
            return lowValue < highValue ? 1 : -1;
        }

        protected static IEnumerable<Point> CreateYield(Int32 x, Int32 y)
        {
            yield return new Point(x, y);
        }

        #endregion

        #region | Horizontal line |

        public static IEnumerable<Point> EnumerateHorizontalLine(Int32 x1, Int32 x2, Int32 y)
        {
            IEnumerable<Point> points = EnumerateHorizontalLine(x2 - x1 + GetDirection(x1, x2));
            return points.Select(point => new Point(point.X + x1, point.Y + y));
        }

        public static IEnumerable<Point> EnumerateHorizontalLine(Int32 width)
        {
            // preliminary check - zero point
            if (width == 0) return CreateYield(0, 0);

            // enumerates horizontal line
            return EnumerateHorizontalLineInternal(width);
        }

        private static IEnumerable<Point> EnumerateHorizontalLineInternal(Int32 width)
        {
            // preliminary check - zero point
            if (width == 0) return CreateYield(0, 0);

            // possible problem - reversed order
            Boolean positive;
            Int32 direction = GetDirection(width, out positive);

            // prepares buffer
            List<Point> result = new List<Point>();

            // enumerates units in a reverse direction
            for (Int32 x = 0; (!positive && x > width) || (positive && x < width); x += direction)
            {
                Point point = new Point(x, 0);
                result.Add(point);
            }

            // returns the points
            return result;
        }

        #endregion

        #region | Vertical line |

        public static IEnumerable<Point> EnumerateVerticalLine(Int32 x, Int32 y1, Int32 y2)
        {
            IEnumerable<Point> points = EnumerateVerticalLine(y2 - y1 + GetDirection(y1, y2));
            return points.Select(point => new Point(point.X + x, point.Y + y1));
        }

        public static IEnumerable<Point> EnumerateVerticalLine(Int32 height, Func<Point, Boolean> stopFunction = null)
        {
            // preliminary check - zero point
            if (height == 0) return CreateYield(0, 0);

            // enumerates vertical line
            return EnumerateVerticalLineInternal(height);
        }

        private static IEnumerable<Point> EnumerateVerticalLineInternal(Int32 height)
        {
            // possible problem - reversed order
            Boolean positive;
            Int32 direction = GetDirection(height, out positive);

            // prepares buffer
            List<Point> result = new List<Point>();

            // enumerates units in a given direction
            for (Int32 y = 0; (!positive && y > height) || (positive && y < height); y += direction)
            {
                Point point = new Point(0, y);
                result.Add(point);
            }

            // returns the points
            return result;
        }

        #endregion

        #region | Line |

        public static IEnumerable<Point> EnumerateLine(Int32 x1, Int32 y1, Int32 x2, Int32 y2)
        {
            IEnumerable<Point> points = EnumerateLine(x2 - x1, y2 - y1);
            return points.Select(point => new Point(point.X + x1, point.Y + y1));
        }

        public static IEnumerable<Point> EnumerateLine(Int32 targetX, Int32 targetY)
        {
            // preliminary check - zero point
            if (targetX == 0 && targetY == 0) return CreateYield(0, 0);

            // preliminary check - vertical line
            if (targetX == 0) return EnumerateVerticalLine(0, 0, targetY);

            // preliminary check - horizontal line
            if (targetY == 0) return EnumerateHorizontalLine(0, targetX, 0);

            // enumerates line
            return EnumerateLineInternal(targetX, targetY);
        }

        private static IEnumerable<Point> EnumerateLineInternal(Int32 targetX, Int32 targetY)
        {
            // initializes the variables of the line
            Int32 count, error;
            Int32 x = 0, y = 0;
            Int32 deltaX = Math.Abs(targetX);
            Int32 deltaY = Math.Abs(targetY);
            Int32 stepLeftX, stepLeftY;
            Int32 stepRightX, stepRightY;
            Int32 stepErrorLeft, stepErrorRight;

            // gradual elevation (angle <= 45°)
            if (deltaX >= deltaY)
            {
                count = deltaX + 1;
                error = (deltaY << 1) - deltaX;
                stepErrorLeft = deltaY << 1;
                stepErrorRight = (deltaY - deltaX) << 1;
                stepLeftX = 1; stepRightX = 1;
                stepLeftY = 0; stepRightY = 1;
            }
            else // steep elevation (angle > 45°)
            {
                count = deltaY + 1;
                error = (deltaX << 1) - deltaY;
                stepErrorLeft = deltaX << 1;
                stepErrorRight = (deltaX - deltaY) << 1;
                stepLeftX = 0; stepRightX = 1;
                stepLeftY = 1; stepRightY = 1;
            }

            // possible problem - reversed horizontal alignment (← instead of →)
            if (targetX < 0)
            {
                stepLeftX = -stepLeftX;
                stepRightX = -stepRightX;
            }

            // possible problem - reversed vertical alignment (↓ instead of ↑)
            if (targetY < 0)
            {
                stepLeftY = -stepLeftY;
                stepRightY = -stepRightY;
            }

            Boolean stepLeftDiagonal = Math.Abs(stepLeftX + stepLeftY) != 1;
            Boolean stepRightDiagonal = Math.Abs(stepRightX + stepRightY) != 1;
            Boolean isLeftDominant = Math.Abs(stepErrorLeft) > Math.Abs(stepErrorRight);

            // enumerates a line using a specific unit
            for (Int32 a = 0; a < count; a++)
            {
                // enqueues the new unit
                yield return new Point(x, y);

                // moves position one step near to end
                if (error < 0)
                {
                    if (stepLeftDiagonal)
                    {
                        if (isLeftDominant)
                        {
                            yield return new Point(x, y + stepLeftY);
                        }
                        else
                        {
                            yield return new Point(x + stepLeftX, y);
                        }
                    }

                    error += stepErrorLeft;
                    x += stepLeftX;
                    y += stepLeftY;
                }
                else
                {
                    if (stepRightDiagonal)
                    {
                        if (isLeftDominant)
                        {
                            yield return new Point(x, y + stepRightY);
                        }
                        else
                        {
                            yield return new Point(x + stepRightX, y);
                        }
                    }

                    error += stepErrorRight;
                    x += stepRightX;
                    y += stepRightY;
                }
            }
        }

        #endregion

        #region | Pathfinding |

        public static IReadOnlyList<PathSegment> ScanPathSegments(Point start, Point end, StopFunction stopFunction)
        {
            List<PathSegment> result = new List<PathSegment>();
            Point firstPoint = Point.Empty;
            Point previousPoint = Point.Empty;
            Boolean isInside = true;

            foreach (Point point in EnumerateLine(start.X, start.Y, end.X, end.Y))
            {
                Boolean doStop = stopFunction(point);

                if (isInside != doStop)
                {
                    if (isInside)
                    {
                        firstPoint = point;
                    }
                    else
                    {
                        PathSegment pathSegment = new PathSegment(firstPoint, previousPoint, point);
                        result.Add(pathSegment);
                    }

                    isInside = !isInside;
                }

                previousPoint = point;
            }

            PathSegment lastPathSegment = new PathSegment(firstPoint, end, Point.Empty);
            result.Add(lastPathSegment);
            return result;
        }

        public static Boolean IsUnblocked(Point start, Point end, StopFunction stopFunction)
        {
            return EnumerateLine(start.X, start.Y, end.X, end.Y).All(point => !stopFunction(point));
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Czech Republic Czech Republic
Contacts: EMAIL - smartk8@gmail.com

Comments and Discussions