// 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 to accelerate dictionary and set lookups. /// Converts seamlessly to and from Unity vectors so you can drop it into serialization-friendly containers without refactors. /// /// /// heatMap = new Dictionary(); /// heatMap[voxel] = 0.75f; /// bool isTracked = heatMap.ContainsKey(new FastVector3Int(4, 2, 6)); /// Vector3Int unityVector = voxel; /// ]]> /// [Serializable] [ProtoContract] public readonly struct FastVector3Int : IEquatable, IEquatable, IEquatable, IEquatable, IComparable, IComparable, IComparable, IComparable, IComparable { /// /// Represents the origin vector (0, 0, 0), useful as a default without allocating new instances. /// /// /// /// FastVector3Int origin = FastVector3Int.zero; /// /// public static readonly FastVector3Int zero = new(0, 0, 0); [ProtoMember(1)] [JsonIgnore] public readonly int x; [ProtoMember(2)] [JsonIgnore] public readonly int y; [ProtoMember(4)] [JsonIgnore] public readonly int z; // Out of order proto is expected [ProtoMember(3)] private readonly int _hash; /// /// Initializes a fast vector with explicit components and a cached hash. /// /// The X component. /// The Y component. /// The Z component. /// /// /// FastVector3Int gridPosition = new FastVector3Int(12, -3, 5); /// /// [JsonConstructor] public FastVector3Int(int x, int y, int z) { this.x = x; this.y = y; this.z = z; _hash = Objects.HashCode(x, y, z); } /// /// Initializes a fast vector from a Unity while caching its hash. /// /// The Unity vector to convert. /// /// /// FastVector3Int cached = new FastVector3Int(new Vector3Int(2, 7, 1)); /// /// public FastVector3Int(Vector3Int vector) : this(vector.x, vector.y, vector.z) { } /// /// Initializes a fast vector with a zero Z component. /// /// The X component. /// The Y component. /// /// /// FastVector3Int planar = new FastVector3Int(3, 4); /// /// public FastVector3Int(int x, int y) : this(x, y, 0) { } /// /// Gets the stored X component. /// /// /// /// int column = voxel.X; /// /// [JsonPropertyName("x")] public int X => x; /// /// Gets the stored Y component. /// /// /// /// int row = voxel.Y; /// /// [JsonPropertyName("y")] public int Y => y; /// /// Gets the stored Z component. /// /// /// /// int level = voxel.Z; /// /// [JsonPropertyName("z")] public int Z => z; /// /// Determines whether two fast vectors have identical components. /// /// The left-hand vector. /// The right-hand vector. /// true when both vectors match. /// /// /// bool matches = FastVector3Int.zero == new FastVector3Int(0, 0, 0); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(FastVector3Int lhs, FastVector3Int rhs) { return lhs.Equals(rhs); } /// /// Determines whether two fast vectors differ. /// /// The left-hand vector. /// The right-hand vector. /// true when the vectors are not equal. /// /// /// bool changed = current != previous; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(FastVector3Int lhs, FastVector3Int rhs) { return !lhs.Equals(rhs); } /// /// Adds two fast vectors component-wise. /// /// The first summand. /// The second summand. /// The component-wise sum. /// /// /// FastVector3Int destination = origin + new FastVector3Int(1, 0, 0); /// /// public static FastVector3Int operator +(FastVector3Int lhs, FastVector3Int rhs) { return new FastVector3Int(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); } /// /// Adds a Unity to a fast vector component-wise. /// /// The fast vector. /// The Unity vector. /// The component-wise sum. /// /// /// FastVector3Int snapped = current + new Vector3Int(0, 1, 0); /// /// public static FastVector3Int operator +(FastVector3Int lhs, Vector3Int rhs) { return new FastVector3Int(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); } /// /// Adds a Unity to the planar components of a fast vector. /// /// The fast vector. /// The two-dimensional Unity vector. /// The component-wise sum with Z preserved. /// /// /// FastVector3Int moved = current + new Vector2Int(2, -1); /// /// public static FastVector3Int operator +(FastVector3Int lhs, Vector2Int rhs) { return new FastVector3Int(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z); } /// /// Adds a to the planar components of a fast vector. /// /// The fast vector. /// The two-dimensional fast vector. /// The component-wise sum with Z preserved. /// /// /// FastVector3Int offset = current + new FastVector2Int(0, 3); /// /// public static FastVector3Int operator +(FastVector3Int lhs, FastVector2Int rhs) { return new FastVector3Int(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z); } /// /// Subtracts one fast vector from another component-wise. /// /// The minuend. /// The subtrahend. /// The component-wise difference. /// /// /// FastVector3Int delta = target - origin; /// /// public static FastVector3Int operator -(FastVector3Int lhs, FastVector3Int rhs) { return new FastVector3Int(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); } /// /// Subtracts a Unity from a fast vector component-wise. /// /// The fast vector. /// The Unity vector. /// The component-wise difference. /// /// /// FastVector3Int offset = current - new Vector3Int(0, 1, 0); /// /// public static FastVector3Int operator -(FastVector3Int lhs, Vector3Int rhs) { return new FastVector3Int(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); } /// /// Subtracts a from the planar components of a fast vector. /// /// The fast vector. /// The two-dimensional fast vector. /// The component-wise difference with Z preserved. /// /// /// FastVector3Int planarDelta = current - new FastVector2Int(5, 1); /// /// public static FastVector3Int operator -(FastVector3Int lhs, FastVector2Int rhs) { return new FastVector3Int(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z); } /// /// Subtracts a Unity from the planar components of a fast vector. /// /// The fast vector. /// The two-dimensional Unity vector. /// The component-wise difference with Z preserved. /// /// /// FastVector3Int adjustment = current - new Vector2Int(3, 0); /// /// public static FastVector3Int operator -(FastVector3Int lhs, Vector2Int rhs) { return new FastVector3Int(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z); } /// /// Converts a fast vector into a Unity . /// /// The fast vector. /// A Unity vector with identical components. /// /// /// Vector3Int unityVector = FastVector3Int.zero; /// /// public static implicit operator Vector3Int(FastVector3Int vector) { return new Vector3Int(vector.x, vector.y, vector.z); } /// /// Projects this fast 3D vector onto the XY plane as a Unity by discarding the Z component. /// /// The fast vector to project. /// A containing the X and Y components. /// /// /// Vector2Int tile = (Vector2Int)new FastVector3Int(5, 9, -2); /// /// public static implicit operator Vector2Int(FastVector3Int vector) { return new Vector2Int(vector.x, vector.y); } /// /// Converts a Unity to a fast vector while caching its hash. /// /// The Unity vector. /// A fast vector with identical components. /// /// /// FastVector3Int cached = new Vector3Int(6, 1, 9); /// /// public static implicit operator FastVector3Int(Vector3Int vector) { return new FastVector3Int(vector.x, vector.y, vector.z); } /// /// Converts a fast vector into Unity's . /// /// The fast vector. /// A with identical components. /// /// /// Vector3 worldPoint = FastVector3Int.zero; /// /// public static implicit operator Vector3(FastVector3Int vector) { return new Vector3(vector.x, vector.y, vector.z); } /// /// Converts a Unity to a fast vector with a zero Z component. /// /// The Unity vector. /// A fast vector containing the planar components. /// /// /// FastVector3Int elevated = new Vector2Int(1, 2); /// /// public static implicit operator FastVector3Int(Vector2Int vector) { return new FastVector3Int(vector.x, vector.y, 0); } /// /// Converts a fast vector into Unity's . /// /// The fast vector. /// A with the X and Y components. /// /// /// Vector2 planar = FastVector3Int.zero; /// /// public static implicit operator Vector2(FastVector3Int vector) { return new Vector2(vector.x, vector.y); } /// /// Returns the cached hash code for the vector so it can participate in hash-based collections without recomputing component hashes. /// /// A deterministic hash based on X, Y, and Z. /// /// occupied = new HashSet(); /// FastVector3Int anchor = new FastVector3Int(1, 2, 3); /// occupied.Add(anchor); /// int hash = anchor.GetHashCode(); /// bool contains = occupied.Contains(new FastVector3Int(1, 2, 3)); /// ]]> /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return _hash; } /// /// Determines equality between this fast vector and a Unity by comparing planar components. /// /// The Unity vector. /// true when X and Y match. /// /// /// bool shareCell = current.Equals(new Vector2Int(7, 1)); /// /// public bool Equals(Vector2Int other) { return x == other.x && y == 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(5, 0)) > 0; /// /// public int CompareTo(Vector2Int other) { int comparison = x.CompareTo(other.x); if (comparison != 0) { return comparison; } return y.CompareTo(other.y); } /// /// Compares this vector to a Unity using lexicographical ordering on X, then Y, then Z. /// /// The Unity vector to compare to. /// A signed integer describing the ordering relationship. /// /// /// FastVector3Int platform = new FastVector3Int(0, 4, 2); /// bool isAbove = platform.CompareTo(new Vector3Int(0, 3, 10)) > 0; /// /// public int CompareTo(Vector3Int other) { int comparison = x.CompareTo(other.x); if (comparison != 0) { return comparison; } comparison = y.CompareTo(other.y); if (comparison != 0) { return comparison; } return z.CompareTo(other.z); } /// /// Determines equality against any supported vector representation. /// /// The candidate vector. /// true when represents the same coordinates. /// /// /// object candidate = new Vector3Int(4, 2, 6); /// bool matches = current.Equals(candidate); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { return obj switch { FastVector3Int vector => Equals(vector), Vector3Int vector => Equals(vector), FastVector2Int vector => Equals(vector), Vector2Int vector => Equals(vector), _ => false, }; } /// /// Determines equality with a Unity instance. /// /// The Unity vector. /// true when all components match. /// /// /// bool identical = current.Equals(new Vector3Int(1, 2, 3)); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector3Int other) { return x == other.x && y == other.y && z == other.z; } /// /// Determines equality with another fast vector. /// /// The other fast vector. /// true when all components match. /// /// /// bool isOrigin = current.Equals(FastVector3Int.zero); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(FastVector3Int other) { return GetHashCode() == other.GetHashCode() && x == other.x && y == other.y && z == other.z; } /// /// Determines equality with a planar by comparing X and Y. /// /// The two-dimensional fast vector. /// true when the planar components match. /// /// /// bool overlaps = current.Equals(new FastVector2Int(5, 3)); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(FastVector2Int other) { return x == other.x && y == other.y; } /// /// Compares this fast vector to another fast vector using lexicographical ordering by X, then Y, then Z. /// /// The other fast vector. /// A signed integer describing the ordering. /// /// /// bool isBefore = current.CompareTo(target) < 0; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(FastVector3Int other) { int xComparison = x.CompareTo(other.x); if (xComparison != 0) { return xComparison; } int yComparison = y.CompareTo(other.y); if (yComparison != 0) { return yComparison; } return z.CompareTo(other.z); } /// /// Compares this fast vector to a by X then Y. /// /// The two-dimensional fast vector. /// A signed integer describing the ordering. /// /// /// bool precedes = current.CompareTo(new FastVector2Int(1, 4)) < 0; /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(FastVector2Int other) { int comparison = x.CompareTo(other.x); if (comparison != 0) { return comparison; } return y.CompareTo(other.y); } /// /// Compares this fast vector to any supported vector representation. /// /// The candidate vector. /// A signed integer describing the ordering, or -1 when unsupported. /// /// /// int ordering = current.CompareTo((object)new Vector3Int(8, 0, 2)); /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(object other) { return other switch { FastVector3Int vector => CompareTo(vector), Vector3Int vector => CompareTo(vector), FastVector2Int vector => CompareTo(vector), Vector2Int vector => CompareTo(vector), _ => -1, }; } /// /// Formats the vector as a string tuple containing X, Y, and Z. /// /// A string formatted as (x, y, z). /// /// /// FastVector3Int checkpoint = new FastVector3Int(-2, 8, 15); /// string label = checkpoint.ToString(); /// /// public override string ToString() { return $"({x}, {y}, {z})"; } /// /// Converts this vector to a by discarding the Z component. /// /// The planar fast vector. /// /// /// FastVector2Int planar = current.FastVector2Int(); /// /// public FastVector2Int FastVector2Int() { return new FastVector2Int(x, y); } /// /// Converts this fast vector to a Unity . /// /// The planar . /// /// /// Vector2 uiPosition = current.AsVector2(); /// /// public Vector2 AsVector2() { return new Vector2(x, y); } /// /// Converts this fast vector to a Unity . /// /// The with identical components. /// /// /// Vector3 worldPoint = current.AsVector3(); /// /// public Vector3 AsVector3() { return new Vector3(x, y, z); } } }