using System.Numerics; using System.Runtime.CompilerServices; using JetBrains.Annotations; using Robust.Shared.Serialization; namespace Content.Shared.Atmos; /// /// Bitflag for representing directions in atmospherics. /// Used for indicating if airflow is allowed/blocked, etc. /// Used over as it allows us to represent multiple valid directions at once, /// plus gives us easy comparisons using bitflags. /// [Flags, Serializable] [FlagsFor(typeof(AtmosDirectionFlags))] public enum AtmosDirection { Invalid = 0, // 0 North = 1 << 0, // 1 South = 1 << 1, // 2 East = 1 << 2, // 4 West = 1 << 3, // 8 // If more directions are added, note that AtmosDirectionHelpers.ToOppositeIndex() expects opposite directions // to come in pairs NorthEast = North | East, // 5 SouthEast = South | East, // 6 NorthWest = North | West, // 9 SouthWest = South | West, // 10 All = North | South | East | West, // 15 } public static class AtmosDirectionHelpers { public static AtmosDirection GetOpposite(this AtmosDirection direction) { return direction switch { AtmosDirection.North => AtmosDirection.South, AtmosDirection.South => AtmosDirection.North, AtmosDirection.East => AtmosDirection.West, AtmosDirection.West => AtmosDirection.East, AtmosDirection.NorthEast => AtmosDirection.SouthWest, AtmosDirection.NorthWest => AtmosDirection.SouthEast, AtmosDirection.SouthEast => AtmosDirection.NorthWest, AtmosDirection.SouthWest => AtmosDirection.NorthEast, _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; } /// /// This returns the index that corresponds to the opposite direction of some other direction index. /// I.e., 1<<OppositeIndex(i) == (1<<i).GetOpposite() /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ToOppositeIndex(this int index) { return index ^ 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static AtmosDirection ToOppositeDir(this int index) { return (AtmosDirection) (1 << (index ^ 1)); } public static Direction ToDirection(this AtmosDirection direction) { return direction switch { AtmosDirection.North => Direction.North, AtmosDirection.South => Direction.South, AtmosDirection.East => Direction.East, AtmosDirection.West => Direction.West, AtmosDirection.NorthEast => Direction.NorthEast, AtmosDirection.NorthWest => Direction.NorthWest, AtmosDirection.SouthEast => Direction.SouthEast, AtmosDirection.SouthWest => Direction.SouthWest, AtmosDirection.Invalid => Direction.Invalid, _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; } public static AtmosDirection ToAtmosDirection(this Direction direction) { return direction switch { Direction.North => AtmosDirection.North, Direction.South => AtmosDirection.South, Direction.East => AtmosDirection.East, Direction.West => AtmosDirection.West, Direction.NorthEast => AtmosDirection.NorthEast, Direction.NorthWest => AtmosDirection.NorthWest, Direction.SouthEast => AtmosDirection.SouthEast, Direction.SouthWest => AtmosDirection.SouthWest, Direction.Invalid => AtmosDirection.Invalid, _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; } /// /// Converts a direction to an angle, where angle is -PI to +PI. /// /// /// public static Angle ToAngle(this AtmosDirection direction) { return direction switch { AtmosDirection.South => Angle.Zero, AtmosDirection.East => new Angle(MathHelper.PiOver2), AtmosDirection.North => new Angle(Math.PI), AtmosDirection.West => new Angle(-MathHelper.PiOver2), AtmosDirection.NorthEast => new Angle(Math.PI*3/4), AtmosDirection.NorthWest => new Angle(-Math.PI*3/4), AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4), AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4), _ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."), }; } /// /// Converts an angle to a cardinal AtmosDirection /// /// /// public static AtmosDirection ToAtmosDirectionCardinal(this Angle angle) { return angle.GetCardinalDir().ToAtmosDirection(); } /// /// Converts an angle to an AtmosDirection /// /// /// public static AtmosDirection ToAtmosDirection(this Angle angle) { return angle.GetDir().ToAtmosDirection(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ToIndex(this AtmosDirection direction) { // This will throw if you pass an invalid direction. Not this method's fault, but yours! return BitOperations.Log2((uint)direction); } /// /// Converts an index to an . /// This is the same as doing (AtmosDirection)(1 << index), but reduces RSI from writing loops. /// /// The 0-based index of the direction. /// Should be 0 for North, 1 for South, 2 for East, and 3 for West. /// The corresponding to the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] [PublicAPI] public static AtmosDirection ToAtmosDirection(this int index) { return (AtmosDirection)(1 << index); } public static AtmosDirection WithFlag(this AtmosDirection direction, AtmosDirection other) { return direction | other; } public static AtmosDirection WithoutFlag(this AtmosDirection direction, AtmosDirection other) { return direction & ~other; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFlagSet(this AtmosDirection direction, AtmosDirection other) { return (direction & other) == other; } /// /// Converts a cardinal direction to a , where the value is the offset in that direction. /// /// The direction to convert. /// Should be a cardinal direction, but will work with diagonals too. /// A where the value is the offset in that direction. /// E.g., North would be (0, -1), South would be (0, 1), etc. [MethodImpl(MethodImplOptions.AggressiveInlining)] [PublicAPI] public static Vector2i CardinalToIntVec(this AtmosDirection dir) { // extract individual bits and compute delta = (positive side) - (negative side). // a bit faster, works on my machine award var b = (byte)dir; var dx = ((b >> 2) & 1) - ((b >> 3) & 1); var dy = ((b >> 0) & 1) - ((b >> 1) & 1); return new Vector2i(dx, dy); } /// /// Offsets the given in the direction of the given . /// /// The origin position. /// The direction to offset in. /// Should be a cardinal direction, but will work with diagonals too. /// >The offset position. E.g., if the direction is North, this will return (pos.X, pos.Y + 1). [MethodImpl(MethodImplOptions.AggressiveInlining)] [PublicAPI] public static Vector2i Offset(this Vector2i pos, AtmosDirection dir) { return pos + dir.CardinalToIntVec(); } } public sealed class AtmosDirectionFlags;