using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Microsoft.Xna.Framework; 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 = Direction2.Up | Direction2.Right, /// /// The down and right direction, or +x, +y. /// [EnumMember] DownRight = Direction2.Down | Direction2.Right, /// /// The up and left direction, or -x, -y. /// [EnumMember] UpLeft = Direction2.Up | Direction2.Left, /// /// The down and left direction, or -x, +y. /// [EnumMember] DownLeft = Direction2.Down | Direction2.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 = Direction2Helper.All.Where(Direction2Helper.IsAdjacent).ToArray(); /// /// The through directions /// public static readonly Direction2[] Diagonals = Direction2Helper.All.Where(Direction2Helper.IsDiagonal).ToArray(); /// /// All directions except /// public static readonly Direction2[] AllExceptNone = Direction2Helper.All.Where(dir => dir != Direction2.None).ToArray(); private static readonly Direction2[] Clockwise = {Direction2.Up, Direction2.UpRight, Direction2.Right, Direction2.DownRight, Direction2.Down, Direction2.DownLeft, Direction2.Left, Direction2.UpLeft}; private static readonly Dictionary ClockwiseLookup = Direction2Helper.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 == Direction2.Up || dir == Direction2.Right || dir == Direction2.Down || dir == Direction2.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 == Direction2.UpRight || dir == Direction2.DownRight || dir == Direction2.UpLeft || dir == Direction2.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 Direction2.Up: return new Point(0, -1); case Direction2.Right: return new Point(1, 0); case Direction2.Down: return new Point(0, 1); case Direction2.Left: return new Point(-1, 0); case Direction2.UpRight: return new Point(1, -1); case Direction2.DownRight: return new Point(1, 1); case Direction2.DownLeft: return new Point(-1, 1); case Direction2.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 Direction2.Up: return Direction2.Down; case Direction2.Right: return Direction2.Left; case Direction2.Down: return Direction2.Up; case Direction2.Left: return Direction2.Right; case Direction2.UpRight: return Direction2.DownLeft; case Direction2.DownRight: return Direction2.UpLeft; case Direction2.DownLeft: return Direction2.UpRight; case Direction2.UpLeft: return Direction2.DownRight; default: return Direction2.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 off = dir.Offset(); return (float) Math.Atan2(off.Y, off.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 (!Direction2Helper.ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return Direction2.None; return Direction2Helper.Clockwise[(dirIndex + (fortyFiveDegrees ? 1 : 2)) % Direction2Helper.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 (!Direction2Helper.ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return Direction2.None; var index = dirIndex - (fortyFiveDegrees ? 1 : 2); return Direction2Helper.Clockwise[index < 0 ? index + Direction2Helper.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 Direction2Helper.AllExceptNone) { if (Math.Abs(dir.Angle() - offsetAngle) <= MathHelper.PiOver4 / 2) return dir; } return Direction2.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 Direction2.None; if (Math.Abs(offset.X) > Math.Abs(offset.Y)) return offset.X > 0 ? Direction2.Right : Direction2.Left; return offset.Y > 0 ? Direction2.Down : Direction2.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 = Direction2.Up) { if (!Direction2Helper.ClockwiseLookup.TryGetValue(reference, out var refIndex)) return Direction2.None; if (!Direction2Helper.ClockwiseLookup.TryGetValue(start, out var startIndex)) return Direction2.None; if (!Direction2Helper.ClockwiseLookup.TryGetValue(dir, out var dirIndex)) return Direction2.None; var diff = refIndex - startIndex; if (diff < 0) diff += Direction2Helper.Clockwise.Length; return Direction2Helper.Clockwise[(dirIndex + diff) % Direction2Helper.Clockwise.Length]; } } }