// MIT License - Copyright (c) 2023 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Math { using System; using System.Runtime.Serialization; using System.Text.Json.Serialization; using Helper; using ProtoBuf; /// /// Inclusive Range [min,max] with configurable endpoint inclusivity. /// /// /// .Inclusive(0, 10); /// bool yes = r.Contains(10); // true (inclusive end) /// var re = Range.Exclusive(0, 10); /// bool no = re.Contains(10); // false (exclusive end) /// ]]> /// [Serializable] [DataContract] [ProtoContract] public struct Range : IEquatable>, IComparable> where T : IEquatable, IComparable { [DataMember] [JsonInclude] [ProtoMember(1)] public T min; [DataMember] [JsonInclude] [ProtoMember(2)] public T max; [DataMember] [JsonInclude] [ProtoMember(3)] public bool startInclusive; [DataMember] [JsonInclude] [ProtoMember(4)] public bool endInclusive; [JsonIgnore] public readonly T Min => min; [JsonIgnore] public readonly T Max => max; [JsonIgnore] public readonly bool StartInclusive => startInclusive; [JsonIgnore] public readonly bool EndInclusive => endInclusive; [JsonConstructor] public Range(T min, T max, bool startInclusive = true, bool endInclusive = true) { if (min.CompareTo(max) > 0) { throw new ArgumentException($"min ({min}) must be <= max ({max})"); } this.min = min; this.max = max; this.startInclusive = startInclusive; this.endInclusive = endInclusive; } public bool Equals(Range other) { return min.Equals(other.min) && max.Equals(other.max) && startInclusive == other.startInclusive && endInclusive == other.endInclusive; } public override bool Equals(object obj) { return obj is Range other && Equals(other); } public override int GetHashCode() { return Objects.HashCode(min, max, startInclusive, endInclusive); } public int CompareTo(Range other) { int minComparison = min.CompareTo(other.min); if (minComparison != 0) { return minComparison; } int maxComparison = max.CompareTo(other.max); if (maxComparison != 0) { return maxComparison; } if (startInclusive != other.startInclusive) { return startInclusive ? -1 : 1; } if (endInclusive != other.endInclusive) { return endInclusive ? -1 : 1; } return 0; } public override string ToString() { char start = startInclusive ? '[' : '('; char end = endInclusive ? ']' : ')'; return $"{start}{min}, {max}{end}"; } public bool WithinRange(T value) { int minComparison = value.CompareTo(min); if (minComparison < 0 || (minComparison == 0 && !startInclusive)) { return false; } int maxComparison = value.CompareTo(max); return maxComparison < 0 || (maxComparison == 0 && endInclusive); } public bool Contains(T value) => WithinRange(value); public bool Overlaps(Range other) { return WithinRange(other.min) || WithinRange(other.max) || other.WithinRange(min) || other.WithinRange(max); } public static Range Inclusive(T min, T max) => new(min, max, true, true); public static Range Exclusive(T min, T max) => new(min, max, false, false); public static Range InclusiveExclusive(T min, T max) => new(min, max, true, false); public static Range ExclusiveInclusive(T min, T max) => new(min, max, false, true); public static bool operator ==(Range left, Range right) => left.Equals(right); public static bool operator !=(Range left, Range right) => !left.Equals(right); } }