// MIT License - Copyright (c) 2023 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.DataStructure.Adapters { using System; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using Helper; using ProtoBuf; using UnityEngine; /// /// Lightweight alternative to Unity's that caches its hash for efficient dictionary and set usage. /// Provides implicit conversions to and from Unity's struct so gameplay code can continue to use the familiar API while /// obtaining stable, allocation-free dictionary keys. /// /// /// labels = new Dictionary(); /// labels[gridCell] = "SpawnPoint"; /// bool hasSpawn = labels.ContainsKey(new FastVector2Int(3, 5)); /// Vector2Int unityVector = gridCell; /// ]]> /// [Serializable] [ProtoContract] public readonly struct FastVector2Int : IEquatable, IEquatable, IEquatable, IEquatable, IComparable, IComparable, IComparable, IComparable, IComparable { /// /// Represents the origin vector (0, 0), useful as a default value without reallocation. /// /// /// /// FastVector2Int startCell = FastVector2Int.zero; /// /// public static readonly FastVector2Int zero = new(0, 0); [ProtoMember(1)] [JsonIgnore] public readonly int x; [ProtoMember(2)] [JsonIgnore] public readonly int y; [ProtoMember(3)] private readonly int _hash; /// /// Initializes a new fast vector with integer components and a cached hash. /// /// The X component. /// The Y component. /// /// /// FastVector2Int waypoint = new FastVector2Int(12, -3); /// /// [JsonConstructor] public FastVector2Int(int x, int y) { this.x = x; this.y = y; _hash = Objects.HashCode(x, y); } /// /// Gets the stored X component. /// /// /// /// int column = waypoint.X; /// /// [JsonPropertyName("x")] public int X => x; /// /// Gets the stored Y component. /// /// /// /// int row = waypoint.Y; /// /// [JsonPropertyName("y")] public int Y => y; /// /// Determines whether two fast vectors are equal by comparing their components. /// /// The left-hand vector. /// The right-hand vector. /// true when both vectors have matching components. /// /// /// bool sameCell = FastVector2Int.zero == new FastVector2Int(0, 0); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(FastVector2Int lhs, FastVector2Int rhs) { return lhs.Equals(rhs); } /// /// Determines whether two fast vectors differ in any component. /// /// The left-hand vector. /// The right-hand vector. /// true when the vectors are not equal. /// /// /// bool hasMoved = currentCell != previousCell; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(FastVector2Int lhs, FastVector2Int rhs) { return !lhs.Equals(rhs); } /// /// Converts a fast vector into Unity's . /// /// The fast vector to convert. /// A Unity vector with identical components. /// /// /// Vector2Int unityVector = FastVector2Int.zero; /// /// public static implicit operator Vector2Int(FastVector2Int vector) { return new Vector2Int(vector.x, vector.y); } /// /// Converts a into a by discarding the Z component. /// /// The three-dimensional fast vector. /// A two-dimensional fast vector containing the X and Y components. /// /// /// FastVector2Int planar = new FastVector3Int(2, 7, 4); /// /// public static implicit operator FastVector2Int(FastVector3Int vector) { return new FastVector2Int(vector.x, vector.y); } /// /// Converts a Unity to a fast vector while caching its hash code. /// /// The Unity vector to convert. /// A new fast vector with identical components. /// /// /// FastVector2Int cached = new Vector2Int(5, 9); /// /// public static implicit operator FastVector2Int(Vector2Int vector) { return new FastVector2Int(vector.x, vector.y); } /// /// Adds two fast vectors component-wise. /// /// The first summand. /// The second summand. /// The component-wise sum. /// /// /// FastVector2Int destination = origin + new FastVector2Int(1, 0); /// /// public static FastVector2Int operator +(FastVector2Int lhs, FastVector2Int rhs) { return new FastVector2Int(lhs.x + rhs.x, lhs.y + rhs.y); } /// /// Subtracts one fast vector from another component-wise. /// /// The minuend. /// The subtrahend. /// The component-wise difference. /// /// /// FastVector2Int offset = target - origin; /// /// public static FastVector2Int operator -(FastVector2Int lhs, FastVector2Int rhs) { return new FastVector2Int(lhs.x - rhs.x, lhs.y - rhs.y); } /// /// Determines whether this fast vector equals another fast vector instance. /// /// The other fast vector. /// true when both vectors have identical components. /// /// /// bool isOrigin = position.Equals(FastVector2Int.zero); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(FastVector2Int other) { return GetHashCode() == other.GetHashCode() && x == other.x && y == other.y; } /// /// Determines whether this fast vector equals a Unity . /// /// The Unity vector. /// true when the X and Y components match. /// /// /// bool matches = position.Equals(new Vector2Int(8, 3)); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector2Int other) { return x == other.x && y == other.y; } /// /// Compares this fast vector to another fast vector using lexicographical ordering. /// /// The other fast vector. /// A signed integer describing the ordering. /// /// /// bool isBefore = current.CompareTo(target) < 0; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(FastVector2Int other) { int comparison = x.CompareTo(other.x); return comparison != 0 ? comparison : y.CompareTo(other.y); } /// /// Compares this fast vector to a Unity using lexicographical ordering. /// /// The Unity vector. /// A signed integer describing the ordering. /// /// /// bool comesAfter = current.CompareTo(new Vector2Int(1, 2)) > 0; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(Vector2Int other) { int comparison = x.CompareTo(other.x); return comparison != 0 ? comparison : y.CompareTo(other.y); } /// /// Determines whether this fast vector equals a , ignoring the Z component. /// /// The other fast vector. /// true when the X and Y components match. /// /// /// bool overlaps = position.Equals(new FastVector3Int(4, 2, 9)); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(FastVector3Int other) { return x == other.x && y == other.y; } /// /// Compares this fast vector to a by X then Y, ignoring Z. /// /// The three-dimensional fast vector. /// A signed integer describing the ordering. /// /// /// bool precedes = current.CompareTo(new FastVector3Int(4, 2, 1)) < 0; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(FastVector3Int other) { int comparison = x.CompareTo(other.x); return comparison != 0 ? comparison : y.CompareTo(other.y); } /// /// Determines whether this fast vector equals a Unity without considering its Z component. /// /// The Unity vector. /// true when the planar components match. /// /// /// bool sharesCell = position.Equals(new Vector3Int(4, 2, 6)); /// /// public bool Equals(Vector3Int other) { return x == other.x && y == other.y; } /// /// Compares this fast vector to a Unity by X then Y components. /// /// The Unity vector. /// A signed integer describing the ordering. /// /// /// bool isLower = current.CompareTo(new Vector3Int(2, 3, 0)) < 0; /// /// public int CompareTo(Vector3Int other) { int comparison = x.CompareTo(other.x); if (comparison != 0) { return comparison; } return y.CompareTo(other.y); } /// /// Determines equality against any supported vector representation. /// /// The candidate vector. /// true when represents the same planar coordinates. /// /// /// object candidate = new Vector2Int(2, 1); /// bool matches = position.Equals(candidate); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { return obj switch { FastVector2Int vector => Equals(vector), Vector2Int vector => Equals(vector), FastVector3Int vector => Equals(vector), Vector3Int vector => Equals(vector), _ => false, }; } /// /// Returns the cached hash code so this vector can be used as a deterministic key in dictionaries and sets. /// /// An integer hash that combines the X and Y components. /// /// visitedCells = new HashSet(); /// FastVector2Int cell = new FastVector2Int(2, 4); /// visitedCells.Add(cell); /// int hash = cell.GetHashCode(); /// bool contains = visitedCells.Contains(new FastVector2Int(2, 4)); /// ]]> /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return _hash; } /// /// Formats the vector as a readable string containing its X and Y components. /// /// A string in the form (x, y). /// /// /// public override string ToString() { return $"({x}, {y})"; } /// /// Compares this fast vector with any supported vector representation. /// /// The candidate vector. /// A signed integer describing the ordering, or -1 when the type is unsupported. /// /// /// int ordering = position.CompareTo((object)new Vector2Int(4, 2)); /// /// public int CompareTo(object obj) { return obj switch { FastVector2Int vector => CompareTo(vector), Vector2Int vector => CompareTo(vector), FastVector3Int vector => CompareTo(vector), Vector3Int vector => CompareTo(vector), _ => -1, }; } /// /// Creates a from this vector with a zero Z component. /// /// The extended fast vector. /// /// /// FastVector3Int elevated = position.AsFastVector3Int(); /// /// public FastVector3Int AsFastVector3Int() { return new FastVector3Int(x, y); } /// /// Converts this fast vector to a Unity . /// /// A with the same components. /// /// /// Vector2 uiPosition = position.AsVector2(); /// /// public Vector2 AsVector2() { return new Vector2(x, y); } /// /// Converts this fast vector to a Unity with a zero Z component. /// /// A that represents the same planar location. /// /// /// Vector3 worldPoint = position.AsVector3(); /// /// public Vector3 AsVector3() { return new Vector3(x, y); } } }