using System; namespace MonoGame.Extended; /// /// Represents a closed mathematical interval [Min, Max] defined by comparable bounds. /// /// /// The type of values contained in the interval. /// public readonly struct Interval : IEquatable> where T : IComparable { private readonly T _min; private readonly T _max; /// /// Gets the minimum bound of the interval. /// /// Thrown when the interval is empty. public T Min { get { if (IsEmpty) { throw new InvalidOperationException("Cannot access bounds of an empty interval"); } return _min; } } /// /// Gets the maximum bound of the interval. /// /// Thrown when the interval is empty. public T Max { get { if (IsEmpty) { throw new InvalidOperationException("Cannot access bounds of an empty interval"); } return _max; } } /// /// Gets a value indicating whether the interval contains no values. /// public bool IsEmpty { get; } /// /// Gets a value indicating whether the interval contains exactly one value. /// public bool IsDegenerate => !IsEmpty && _min.Equals(_max); /// /// Gets a value indicating whether the interval contains more than one value. /// public bool IsProper => !IsEmpty && !_min.Equals(_max); /// /// Gets an empty interval. /// public static Interval Empty => new(true); /// /// Initializes an interval with the specified bounds. /// /// The minimum bound of the interval. /// The maximum bound of the interval. /// Thrown when min is greater than max. public Interval(T min, T max) { if (min.CompareTo(max) > 0) { throw new ArgumentException("Minimum bounds cannot be greater than maximum bounds"); } _min = min; _max = max; IsEmpty = false; } /// /// Initializes a degenerate interval containing only the specified value. /// /// The single value to be contained in the interval. public Interval(T value) : this(value, value) { } private Interval(bool isEmpty) { _min = default; _max = default; IsEmpty = true; } /// /// Determines whether the interval contains the specified value. /// /// The value to test for containment. /// true if the value is within the interval bounds; otherwise, false. public bool Contains(T value) { if (IsEmpty) { return false; } return value.CompareTo(_min) >= 0 && value.CompareTo(_max) <= 0; } /// /// Determines whether this interval completely contains another interval. /// /// The interval to test for containment. /// true if this interval completely contains the other interval; otherwise, false. public bool Contains(Interval other) { if (other.IsEmpty) { return true; } if (IsEmpty) { return false; } return _min.CompareTo(other._min) <= 0 && _max.CompareTo(other._max) >= 0; } /// /// Determines whether this interval shares any values with another interval. /// /// The interval to test for overlap. /// true if the intervals have any values in common; otherwise, false. public bool Overlap(Interval other) { if (IsEmpty || other.IsEmpty) { return false; } return _min.CompareTo(other._max) <= 0 && _max.CompareTo(other._min) >= 0; } /// /// Computes the intersection of this interval with another interval. /// /// The interval to intersect with. /// An interval containing only values present in both intervals, or empty if no intersection exists. public Interval Intersect(Interval other) { if (IsEmpty || other.IsEmpty || !Overlap(other)) { return Empty; } T minBound = _min.CompareTo(other._min) >= 0 ? _min : other._min; T maxBound = _max.CompareTo(other._max) <= 0 ? _max : other._max; return new Interval(minBound, maxBound); } /// /// Computes the smallest interval containing both this interval and another interval. /// /// The interval to compute the hull with. /// The smallest interval that contains both intervals. public Interval Hull(Interval other) { if (IsEmpty) { return other; } if (other.IsEmpty) { return this; } T minBound = _min.CompareTo(other._min) <= 0 ? _min : other._min; T maxBound = _max.CompareTo(other._max) >= 0 ? _max : other._max; return new Interval(minBound, maxBound); } /// /// Creates an interval that spans both specified values. /// /// The first value. /// The second value. /// An interval from the minimum to maximum of the two values. public static Interval Hull(T a, T b) { if (a.CompareTo(b) <= 0) { return new Interval(a, b); } else { return new Interval(b, a); } } /// public override bool Equals(object obj) => obj is Interval other && Equals(other); /// public bool Equals(Interval other) { if (IsEmpty && other.IsEmpty) { return true; } if (IsEmpty && !other.IsEmpty) { return false; } return _min.Equals(other._min) && _max.Equals(other._max); } /// public override int GetHashCode() { if (IsEmpty) { return 0; } return HashCode.Combine(_min, _max); } /// public static bool operator ==(Interval left, Interval right) => left.Equals(right); /// public static bool operator !=(Interval left, Interval right) => !left.Equals(right); /// public static implicit operator Interval(T value) => new(value); /// public override string ToString() { if (IsEmpty) { return "∅"; } if (IsDegenerate) { return $"[{_min}]"; } return $"[{_min}, {_max}]"; } }