#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Immutable; using System.Linq; namespace OpenRA.Mods.Common.MapGenerator { /// /// Utilities for simple directions and adjacency. Note that coordinate systems might not agree /// as to which directions are conceptually left/right or up/down. Direction is typically used /// with the CPos coordinate system. /// public enum Direction { /// No direction. None = -1, /// +X ("right"). R = 0, /// +X+Y ("right down"). RD = 1, /// +Y ("down"). D = 2, /// -X+Y ("left down"). LD = 3, /// -X ("left"). L = 4, /// -X-Y ("left up"). LU = 5, /// -Y ("up"). U = 6, /// +X-Y ("right up"). RU = 7, } [Flags] public enum DirectionMask { None = 0, /// Bitmask right. MR = 1 << Direction.R, /// Bitmask right-down. MRD = 1 << Direction.RD, /// Bitmask down. MD = 1 << Direction.D, /// Bitmask left-down. MLD = 1 << Direction.LD, /// Bitmask left. ML = 1 << Direction.L, /// Bitmask left-up. MLU = 1 << Direction.LU, /// Bitmask up. MU = 1 << Direction.U, /// Bitmask right-up. MRU = 1 << Direction.RU, } public static class DirectionExts { /// Adjacent offsets with directions, excluding diagonals. public static readonly ImmutableArray<(int2, Direction)> Spread4D = [ (new int2(1, 0), Direction.R), (new int2(0, 1), Direction.D), (new int2(-1, 0), Direction.L), (new int2(0, -1), Direction.U) ]; /// Adjacent offsets, excluding diagonals. public static readonly ImmutableArray Spread4 = Spread4D.Select(((int2 XY, Direction _) v) => v.XY).ToImmutableArray(); /// /// Adjacent offsets, excluding diagonals. Assumes that CVec(1, 0) /// corresponds to Direction.R. /// public static readonly ImmutableArray Spread4CVec = Spread4.Select(xy => new CVec(xy.X, xy.Y)).ToImmutableArray(); /// Adjacent offsets with directions, including diagonals. public static readonly ImmutableArray<(int2, Direction)> Spread8D = [ (new int2(1, 0), Direction.R), (new int2(1, 1), Direction.RD), (new int2(0, 1), Direction.D), (new int2(-1, 1), Direction.LD), (new int2(-1, 0), Direction.L), (new int2(-1, -1), Direction.LU), (new int2(0, -1), Direction.U), (new int2(1, -1), Direction.RU) ]; /// Adjacent offsets, including diagonals. public static readonly ImmutableArray Spread8 = Spread8D.Select(((int2 XY, Direction _) v) => v.XY).ToImmutableArray(); /// /// Adjacent offsets, including diagonals. Assumes that CVec(1, 0) /// corresponds to Direction.R. /// public static readonly ImmutableArray Spread8CVec = Spread8.Select(xy => new CVec(xy.X, xy.Y)).ToImmutableArray(); /// Convert a non-none direction to an int2 offset. public static int2 ToInt2(this Direction direction) { if (direction >= Direction.R && direction <= Direction.RU) return Spread8[(int)direction]; else throw new ArgumentException("bad direction"); } /// /// Convert a non-none direction to a CVec offset. Assumes that /// CVec(1, 0) corresponds to Direction.R. /// public static CVec ToCVec(this Direction direction) { if (direction >= Direction.R && direction <= Direction.RU) return Spread8CVec[(int)direction]; else throw new ArgumentException("bad direction"); } /// /// Convert an offset (of arbitrary non-zero magnitude) to a direction. /// The direction is based purely on the signs of the inputs. /// Supplying a zero-offset will throw. /// public static Direction FromOffset(int dx, int dy) { if (dx > 0) { if (dy > 0) return Direction.RD; else if (dy < 0) return Direction.RU; else return Direction.R; } else if (dx < 0) { if (dy > 0) return Direction.LD; else if (dy < 0) return Direction.LU; else return Direction.L; } else { if (dy > 0) return Direction.D; else if (dy < 0) return Direction.U; else throw new ArgumentException("Bad direction"); } } /// /// Convert an offset (of arbitrary non-zero magnitude) to a direction. /// The direction with the closest angle wins. Keep inputs to 1000000 or less. /// Supplying a zero-offset will throw. /// public static Direction ClosestFrom(int dx, int dy) { if (dx == 0 && dy == 0) throw new ArgumentException("bad direction"); var absX = Math.Abs(dx); var absY = Math.Abs(dy); var min = Math.Min(absX, absY); var max = Math.Max(absX, absY); // 408 / 985 is an approximation of tan(Pi / 8 radians), or tan(22.5 degrees) if (408 * max < 985 * min) { // Diagonal return FromOffset(dx, dy); } else { // Cardinal if (absX > absY) return FromOffsetNonDiagonal(dx, 0); else return FromOffsetNonDiagonal(0, dy); } } /// /// Convert an offset (of arbitrary non-zero magnitude) to a direction. /// Supplying a zero-offset will throw. /// public static Direction FromInt2(int2 delta) => FromOffset(delta.X, delta.Y); /// /// Convert an offset (of arbitrary non-zero magnitude) to a direction. /// Supplying a zero-offset will throw. Assumes that CVec(1, 0) /// corresponds to Direction.R. /// public static Direction FromCVec(CVec delta) => FromOffset(delta.X, delta.Y); /// /// Convert an offset (of arbitrary non-zero magnitude) to a direction. /// Supplying a zero-offset will throw. Assumes that CVec(1, 0) /// corresponds to Direction.R. /// public static Direction ClosestFromCVec(CVec delta) => ClosestFrom(delta.X, delta.Y); /// /// Convert an offset (of arbitrary non-zero magnitude) to a non-diagonal direction. /// Supplying a zero-offset will throw. /// public static Direction FromOffsetNonDiagonal(int dx, int dy) { if (dx - dy > 0 && dx + dy >= 0) return Direction.R; if (dy + dx > 0 && dy - dx >= 0) return Direction.D; if (-dx + dy > 0 && -dx - dy >= 0) return Direction.L; if (-dy - dx > 0 && -dy + dx >= 0) return Direction.U; throw new ArgumentException("bad direction"); } /// /// Convert an offset (of arbitrary non-zero magnitude) to a /// non-diagonal direction. Supplying a zero-offset will throw. /// public static Direction FromInt2NonDiagonal(int2 delta) => FromOffsetNonDiagonal(delta.X, delta.Y); /// /// Convert an offset (of arbitrary non-zero magnitude) to a /// non-diagonal direction. Supplying a zero-offset will throw. Assumes /// that CVec(1, 0) corresponds to Direction.R. /// public static Direction FromCVecNonDiagonal(CVec delta) => FromOffsetNonDiagonal(delta.X, delta.Y); /// Return the opposite direction. public static Direction Reverse(this Direction direction) { if (direction != Direction.None) return (Direction)((int)direction ^ 4); else return Direction.None; } /// Converts the direction to a mask value. public static DirectionMask ToMask(this Direction direction) { if (direction >= Direction.R && direction <= Direction.RU) return (DirectionMask)(1 << (int)direction); else return DirectionMask.None; } } public static class DirectionMaskExts { /// Count the number of set bits (directions) in a direction mask. public static int Count(this DirectionMask mask) { return int.PopCount((int)mask); } /// Finds the only direction set in a direction mask or returns None. public static Direction ToDirection(this DirectionMask mask) { var d = int.Log2((int)mask); if (1 << d == (int)mask) return (Direction)d; else return Direction.None; } /// True if diagonal, false if horizontal/vertical, throws otherwise. public static bool IsDiagonal(this Direction direction) { if (direction >= Direction.R && direction <= Direction.RU) return ((int)direction & 1) == 1; else throw new ArgumentException("None or bad direction"); } } }