using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Microsoft.Xna.Framework; using static MLEM.Misc.Direction2; namespace MLEM.Misc { /// /// An enum that represents two-dimensional directions. /// Both straight and diagonal directions are supported. /// There are several extension methods and arrays available in . /// [Flags, DataContract] public enum Direction2 { /// /// No direction. /// [EnumMember] None = 0, /// /// The up direction, or -y. /// [EnumMember] Up = 1, /// /// The right direction, or +x. /// [EnumMember] Right = 2, /// /// The down direction, or +y. /// [EnumMember] Down = 4, /// /// The left direction, or -x. /// [EnumMember] Left = 8, /// /// The up and right direction, or +x, -y. /// [EnumMember] UpRight = Up | Right, /// /// The down and right direction, or +x, +y. /// [EnumMember] DownRight = Down | Right, /// /// The up and left direction, or -x, -y. /// [EnumMember] UpLeft = Up | Left, /// /// The down and left direction, or -x, +y. /// [EnumMember] DownLeft = Down | Left } /// /// A set of helper and extension methods for dealing with /// public static class Direction2Helper { /// /// All enum values /// public static readonly Direction2[] All = EnumHelper.GetValues().ToArray(); /// /// The through directions /// public static readonly Direction2[] Adjacent = All.Where(IsAdjacent).ToArray(); /// /// The through directions /// public static readonly Direction2[] Diagonals = All.Where(IsDiagonal).ToArray(); /// /// All directions except /// public static readonly Direction2[] AllExceptNone = All.Where(dir => dir != None).ToArray(); private static readonly Direction2[] Clockwise = {Up, UpRight, Right, DownRight, Down, DownLeft, Left, UpLeft}; private static readonly Dictionary ClockwiseLookup = Clockwise.Select((d, i) => (d, i)).ToDictionary(kv => kv.d, kv => kv.i); /// /// Returns if the given direction is considered an "adjacent" direction. /// An adjacent direction is one that is not a diagonal. /// /// The direction to query /// Whether the direction is adjacent public static bool IsAdjacent(this Direction2 dir) { return dir == Up || dir == Right || dir == Down || dir == Left; } /// /// Returns if the given direction is considered a diagonal direction. /// /// The direction to query /// Whether the direction is diagonal public static bool IsDiagonal(this Direction2 dir) { return dir == UpRight || dir == DownRight || dir == UpLeft || dir == DownLeft; } /// /// Returns the directional offset of a given direction. /// The offset direction will be exactly one unit in each axis that the direction represents. /// /// The direction whose offset to query /// The direction's offset public static Point Offset(this Direction2 dir) { switch (dir) { case Up: return new Point(0, -1); case Right: return new Point(1, 0); case Down: return new Point(0, 1); case Left: return new Point(-1, 0); case UpRight: return new Point(1, -1); case DownRight: return new Point(1, 1); case DownLeft: return new Point(-1, 1); case UpLeft: return new Point(-1, -1); default: return Point.Zero; } } /// /// Maps each direction in the given enumerable of directions to its . /// /// The direction enumerable /// The directions' offsets, in the same order as the input directions. public static IEnumerable Offsets(this IEnumerable directions) { foreach (var dir in directions) yield return dir.Offset(); } /// /// Returns the opposite of the given direction. /// For "adjacent" directions, this is the direction that points into the same axis, but the opposite sign. /// For diagonal directions, this is the direction that points into the opposite of both the x and y axis. /// /// The direction whose opposite to get /// The opposite of the direction public static Direction2 Opposite(this Direction2 dir) { switch (dir) { case Up: return Down; case Right: return Left; case Down: return Up; case Left: return Right; case UpRight: return DownLeft; case DownRight: return UpLeft; case DownLeft: return UpRight; case UpLeft: return DownRight; default: return None; } } /// /// Returns the angle of the direction in radians, where has an angle of 0. /// /// The direction whose angle to get /// The direction's angle public static float Angle(this Direction2 dir) { var (x, y) = dir.Offset(); return (float) Math.Atan2(y, x); } /// /// Rotates the given direction clockwise and returns the resulting direction. /// /// The direction to rotate /// Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead. /// The rotated direction public static Direction2 RotateCw(this Direction2 dir, bool fortyFiveDegrees = false) { if (!ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return None; return Clockwise[(dirIndex + (fortyFiveDegrees ? 1 : 2)) % Clockwise.Length]; } /// /// Rotates the given direction counter-clockwise and returns the resulting direction. /// /// The direction to rotate counter-clockwise /// Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead. /// The rotated direction public static Direction2 RotateCcw(this Direction2 dir, bool fortyFiveDegrees = false) { if (!ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return None; var index = dirIndex - (fortyFiveDegrees ? 1 : 2); return Clockwise[index < 0 ? index + Clockwise.Length : index]; } /// /// Returns the that is closest to the given position's facing direction. /// /// The vector whose corresponding direction to get /// The vector's direction public static Direction2 ToDirection(this Vector2 offset) { var offsetAngle = (float) Math.Atan2(offset.Y, offset.X); foreach (var dir in AllExceptNone) { if (Math.Abs(dir.Angle() - offsetAngle) <= MathHelper.PiOver4 / 2) return dir; } return None; } /// /// Returns the that is closest to the given position's facing direction, only taking directions into account. /// Diagonal directions will be rounded to the nearest vertical direction. /// /// The vector whose corresponding direction to get /// The vector's direction public static Direction2 To90Direction(this Vector2 offset) { if (offset.X == 0 && offset.Y == 0) return None; if (Math.Abs(offset.X) > Math.Abs(offset.Y)) return offset.X > 0 ? Right : Left; return offset.Y > 0 ? Down : Up; } /// /// Rotates the given direction by a given reference direction /// /// The direction to rotate /// The direction to rotate by /// The direction to use as the default direction /// The direction, rotated by the reference direction public static Direction2 RotateBy(this Direction2 dir, Direction2 reference, Direction2 start = Up) { if (!ClockwiseLookup.TryGetValue(reference, out var refIndex)) return None; if (!ClockwiseLookup.TryGetValue(start, out var startIndex)) return None; if (!ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return None; var diff = refIndex - startIndex; if (diff < 0) diff += Clockwise.Length; return Clockwise[(dirIndex + diff) % Clockwise.Length]; } } }