namespace Theriapolis.Core.Util; /// /// Direction encoding for tile-level features (rivers, rail, roads). /// Directions are stored as byte values 0–7, matching the 8 compass points. /// 255 = "no direction" / unset. /// public static class Dir { // Direction constants (0-based, CCW from North) public const byte None = 255; public const byte N = 0; // (dx= 0, dy=-1) public const byte NE = 1; // (dx= 1, dy=-1) public const byte E = 2; // (dx= 1, dy= 0) public const byte SE = 3; // (dx= 1, dy= 1) public const byte S = 4; // (dx= 0, dy= 1) public const byte SW = 5; // (dx=-1, dy= 1) public const byte W = 6; // (dx=-1, dy= 0) public const byte NW = 7; // (dx=-1, dy=-1) private static readonly (int dx, int dy)[] _deltas = { ( 0,-1), ( 1,-1), ( 1, 0), ( 1, 1), ( 0, 1), (-1, 1), (-1, 0), (-1,-1), }; /// Convert a (dx, dy) delta to a direction byte. Both values must be in {-1,0,1}. public static byte FromDelta(int dx, int dy) { for (byte i = 0; i < 8; i++) if (_deltas[i].dx == dx && _deltas[i].dy == dy) return i; return None; } public static (int dx, int dy) ToDelta(byte dir) { if (dir == None) return (0, 0); return _deltas[dir & 7]; } /// /// Two directions are parallel if their angular difference is ≤ 45° (including opposite directions). /// public static bool IsParallel(byte a, byte b) { if (a == None || b == None) return false; int diff = Math.Abs((int)(a & 7) - (int)(b & 7)); diff = Math.Min(diff, 8 - diff); // diff 0 = same; diff 1 = 45°; diff 4 = 180° (opposite still parallel) return diff <= 1 || diff >= 3; // ≤45° or ≥135° (anti-parallel) } /// /// Two directions are perpendicular if their angular difference is in [60°, 120°]. /// Using discrete 45° steps: diff 2 = 90°. /// public static bool IsPerpendicular(byte a, byte b) { if (a == None || b == None) return false; int diff = Math.Abs((int)(a & 7) - (int)(b & 7)); diff = Math.Min(diff, 8 - diff); return diff == 2; } /// Opposite direction. public static byte Opposite(byte d) => d == None ? None : (byte)((d + 4) & 7); }