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}]";
}
}