mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
470 lines
9.7 KiB
C#
470 lines
9.7 KiB
C#
using System.Collections;
|
|
using System.Text;
|
|
|
|
namespace AssetRipper.Numerics;
|
|
|
|
/// <summary>
|
|
/// An immutable structure representing a discontinuous, possibly empty, range of objects.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public readonly struct DiscontinuousRange<T> : IEquatable<DiscontinuousRange<T>>, IEquatable<Range<T>>, IReadOnlyList<Range<T>>
|
|
where T : notnull, IComparable<T>, IEquatable<T>
|
|
{
|
|
private static readonly List<Range<T>> defaultRangeList = new List<Range<T>>(0);
|
|
private readonly List<Range<T>> rangeList;
|
|
|
|
public static DiscontinuousRange<T> Empty { get; } = new();
|
|
|
|
public DiscontinuousRange()
|
|
{
|
|
rangeList = defaultRangeList;
|
|
}
|
|
|
|
public DiscontinuousRange(Range<T> range)
|
|
{
|
|
rangeList = new List<Range<T>>(1) { range };
|
|
}
|
|
|
|
public DiscontinuousRange(IEnumerable<Range<T>> ranges)
|
|
{
|
|
rangeList = new();
|
|
Add(ranges);
|
|
if (rangeList.Count == 0)
|
|
{
|
|
rangeList = defaultRangeList;
|
|
}
|
|
}
|
|
|
|
public DiscontinuousRange(IReadOnlyList<Range<T>> ranges)
|
|
{
|
|
if (ranges.Count == 0)
|
|
{
|
|
rangeList = defaultRangeList;
|
|
}
|
|
else
|
|
{
|
|
rangeList = new List<Range<T>>(ranges.Count);
|
|
Add(ranges);
|
|
}
|
|
}
|
|
|
|
public DiscontinuousRange(params Range<T>[] ranges)
|
|
{
|
|
rangeList = new List<Range<T>>(ranges.Length);
|
|
Add(ranges);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used only for internal creations of the range list.
|
|
/// </summary>
|
|
/// <param name="ranges"></param>
|
|
private DiscontinuousRange(List<Range<T>> rangeList)
|
|
{
|
|
this.rangeList = rangeList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor for the union of two ranges.
|
|
/// </summary>
|
|
/// <param name="range1"></param>
|
|
/// <param name="range2"></param>
|
|
public DiscontinuousRange(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
|
|
{
|
|
if (range1.Count == 0)
|
|
{
|
|
rangeList = range2.rangeList;//prevent unnecessary allocation of an additional list
|
|
}
|
|
else if (range2.Count == 0)
|
|
{
|
|
rangeList = range1.rangeList;//prevent unnecessary allocation of an additional list
|
|
}
|
|
else
|
|
{
|
|
rangeList = new List<Range<T>>(range1.Count);
|
|
rangeList.AddRange(range1.rangeList);//Copy the elements of range1
|
|
Add(range2.rangeList);//Add the elements of range2
|
|
}
|
|
}
|
|
|
|
public int Count => rangeList.Count;
|
|
|
|
public Range<T> this[int index] => rangeList[index];
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return obj is Range<T> range && Equals(range)
|
|
|| obj is DiscontinuousRange<T> discontinuousRange && Equals(discontinuousRange);
|
|
}
|
|
|
|
public bool Equals(Range<T> other)
|
|
{
|
|
return Count == 1 && this[0] == other;
|
|
}
|
|
|
|
public bool Equals(DiscontinuousRange<T> other)
|
|
{
|
|
if (Count != other.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < rangeList.Count; i++)
|
|
{
|
|
if (!this[i].Equals(other[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool Contains(T point)
|
|
{
|
|
return rangeList.Any(r => r.Contains(point));
|
|
}
|
|
|
|
public bool Contains(Range<T> range)
|
|
{
|
|
return rangeList.Any(r => r.Contains(range));
|
|
}
|
|
|
|
public bool Contains(DiscontinuousRange<T> other)
|
|
{
|
|
foreach (Range<T> range in other.rangeList)
|
|
{
|
|
if (!Contains(range))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool Intersects(Range<T> range)
|
|
{
|
|
return rangeList.Any(r => r.Intersects(range));
|
|
}
|
|
|
|
public bool Intersects(DiscontinuousRange<T> other)
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
while (i < Count && j < other.Count)
|
|
{
|
|
Range<T> thisRange = this[i];
|
|
Range<T> otherRange = other[j];
|
|
if (thisRange.Intersects(otherRange))
|
|
{
|
|
return true;
|
|
}
|
|
else if (thisRange.IsStrictlyLess(otherRange))
|
|
{
|
|
i++;
|
|
}
|
|
else//otherRange is strictly less than thisRange
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
HashCode hc = new();
|
|
for (int i = Count - 1; i >= 0; i--)
|
|
{
|
|
hc.Add(this[i]);
|
|
}
|
|
return hc.ToHashCode();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only used during initialization
|
|
/// </summary>
|
|
/// <param name="range"></param>
|
|
private void Add(Range<T> range)
|
|
{
|
|
int firstUnionIndex = Count;
|
|
int lastUnionIndex = Count;
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
if (this[i].CanUnion(range))
|
|
{
|
|
if (firstUnionIndex == Count)
|
|
{
|
|
firstUnionIndex = i;
|
|
}
|
|
lastUnionIndex = i;
|
|
}
|
|
else if (firstUnionIndex < Count)
|
|
{
|
|
break;
|
|
}
|
|
else if (rangeList[i].IsStrictlyGreater(range))
|
|
{
|
|
rangeList.Insert(i, range);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (firstUnionIndex == Count)
|
|
{
|
|
rangeList.Add(range);
|
|
}
|
|
else
|
|
{
|
|
rangeList[firstUnionIndex] = rangeList[firstUnionIndex].MakeUnion(range);
|
|
if (firstUnionIndex < lastUnionIndex)
|
|
{
|
|
rangeList.RemoveRange(firstUnionIndex + 1, lastUnionIndex - firstUnionIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only used during initialization
|
|
/// </summary>
|
|
/// <param name="ranges"></param>
|
|
private void Add(IEnumerable<Range<T>> ranges)
|
|
{
|
|
foreach (Range<T> range in ranges)
|
|
{
|
|
Add(range);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only used during initialization
|
|
/// </summary>
|
|
/// <param name="ranges"></param>
|
|
private void Add(Range<T>[] ranges)
|
|
{
|
|
for (int i = 0; i < ranges.Length; i++)
|
|
{
|
|
Add(ranges[i]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This range contains no points and is equal to the <see cref="Empty"/> range.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool IsEmpty() => Count == 0;
|
|
|
|
/// <summary>
|
|
/// This range is continuous.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="Empty"/> range is treated as not continuous.
|
|
/// </remarks>
|
|
/// <returns></returns>
|
|
public bool IsContinuous() => Count == 1;
|
|
|
|
/// <summary>
|
|
/// This range is continuous.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="Empty"/> range is treated as not continuous.
|
|
/// </remarks>
|
|
/// <param name="range"></param>
|
|
/// <returns></returns>
|
|
public bool IsContinuous(out Range<T> range)
|
|
{
|
|
if (Count == 1)
|
|
{
|
|
range = this[0];
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
range = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public DiscontinuousRange<T> Negate(T minimum, T maximum)
|
|
{
|
|
if (IsEmpty())
|
|
{
|
|
return new DiscontinuousRange<T>(new Range<T>(minimum, maximum));
|
|
}
|
|
else
|
|
{
|
|
List<Range<T>> newRangeList = new List<Range<T>>(Count + 1);//Count + 1 is the maximum possible size of this list.
|
|
|
|
T start = rangeList[0].Start;
|
|
if (!start.Equals(minimum))
|
|
{
|
|
newRangeList.Add(new Range<T>(minimum, start));
|
|
}
|
|
|
|
for (int i = 0; i < Count - 1; i++)
|
|
{
|
|
newRangeList.Add(new Range<T>(rangeList[i].End, rangeList[i].Start));
|
|
}
|
|
|
|
T end = rangeList[Count - 1].End;
|
|
if (!end.Equals(maximum))
|
|
{
|
|
newRangeList.Add(new Range<T>(end, maximum));
|
|
}
|
|
|
|
return new DiscontinuousRange<T>(newRangeList);
|
|
}
|
|
}
|
|
|
|
public IEnumerator<Range<T>> GetEnumerator() => rangeList.GetEnumerator();
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
public static bool operator ==(DiscontinuousRange<T> left, DiscontinuousRange<T> right)
|
|
{
|
|
return left.Equals(right);
|
|
}
|
|
|
|
public static bool operator !=(DiscontinuousRange<T> left, DiscontinuousRange<T> right)
|
|
{
|
|
return !(left == right);
|
|
}
|
|
|
|
public static implicit operator DiscontinuousRange<T>(Range<T> range) => new DiscontinuousRange<T>(range);
|
|
|
|
public static explicit operator Range<T>(DiscontinuousRange<T> range)
|
|
{
|
|
return range.IsContinuous(out Range<T> continuousRange)
|
|
? continuousRange
|
|
: throw new ArgumentException($"{nameof(range)} is not continuous.", nameof(range));
|
|
}
|
|
|
|
public static DiscontinuousRange<T> Union(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
|
|
{
|
|
return new DiscontinuousRange<T>(range1, range2);
|
|
}
|
|
|
|
public DiscontinuousRange<T> Union(DiscontinuousRange<T> other)
|
|
{
|
|
return new DiscontinuousRange<T>(this, other);
|
|
}
|
|
|
|
public static DiscontinuousRange<T> Intersect(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
|
|
{
|
|
return range1.Intersect(range2);
|
|
}
|
|
|
|
public DiscontinuousRange<T> Intersect(DiscontinuousRange<T> other)
|
|
{
|
|
List<Range<T>> ranges = new();
|
|
int i = 0;
|
|
int j = 0;
|
|
while (i < Count && j < other.Count)
|
|
{
|
|
Range<T> thisRange = this[i];
|
|
Range<T> otherRange = other[j];
|
|
if (thisRange.Intersects(otherRange, out Range<T> intersection))
|
|
{
|
|
ranges.Add(intersection);
|
|
if (thisRange.End.CompareTo(otherRange.End) <= 0)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
else if (thisRange.IsStrictlyLess(otherRange))
|
|
{
|
|
i++;
|
|
}
|
|
else//otherRange is strictly less than thisRange
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
return new DiscontinuousRange<T>(ranges);
|
|
}
|
|
|
|
public DiscontinuousRange<T> Subtract(DiscontinuousRange<T> other)
|
|
{
|
|
List<Range<T>> ranges = new();
|
|
int i = 0;
|
|
int j = 0;
|
|
bool endedInside = false;
|
|
T? nextStartingPoint = default;
|
|
while (i < Count && j < other.Count)
|
|
{
|
|
Range<T> thisRange = this[i];
|
|
Range<T> otherRange = other[j];
|
|
if (thisRange.IsStrictlyLess(otherRange))
|
|
{
|
|
if (endedInside)
|
|
{
|
|
ranges.Add(new Range<T>(nextStartingPoint!, thisRange.End));
|
|
endedInside = false;
|
|
nextStartingPoint = default;
|
|
}
|
|
i++;
|
|
}
|
|
else if (thisRange.IsStrictlyGreater(otherRange))
|
|
{
|
|
j++;
|
|
}
|
|
else
|
|
{
|
|
if (thisRange.Start.IsLess(otherRange.Start))
|
|
{
|
|
if (endedInside)
|
|
{
|
|
ranges.Add(new Range<T>(nextStartingPoint!, otherRange.Start));
|
|
endedInside = false;
|
|
nextStartingPoint = default;
|
|
}
|
|
else
|
|
{
|
|
ranges.Add(new Range<T>(thisRange.Start, otherRange.Start));
|
|
}
|
|
}
|
|
if (thisRange.End.IsLessEqual(otherRange.End))
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
if (j < other.Count - 1)
|
|
{
|
|
endedInside = true;
|
|
nextStartingPoint = otherRange.End;
|
|
}
|
|
else
|
|
{
|
|
ranges.Add(new Range<T>(otherRange.End, thisRange.End));
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
return new DiscontinuousRange<T>(ranges);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
if (Count == 0)
|
|
{
|
|
return "Empty";
|
|
}
|
|
else
|
|
{
|
|
StringBuilder sb = new();
|
|
sb.Append(this[0]);
|
|
for (int i = 1; i < Count; i++)
|
|
{
|
|
sb.Append(", ");
|
|
sb.Append(this[i]);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
}
|