Utilities: Ranged Values

0

Another day, another utility. This time: Ranged values. They were inspired from a Unite Europe 2016 talk on scriptable objects (and the Inpector code is copied nearly verbatim) by Richard Fine. The original had only a minimum and maximum float value with the fancy Inspector GUI, but I extended it with functionality nobody in their right mind would probably ever need. It started with a method to get a random value from within the interval and quickly escalated to two interpolation functions and tests to determine if an interval contains a given value or another interval, or if two intervals intersect. Finally I added operators to add, multiply or order intervals. And as if all that wasn’t already useless enough, I did the same thing again, with ints instead of floats and added a generic Range<T> class, that can create intervals from any orderable value-type, albeit a bit less powerfull, since adding and subtracting doesn’t necessarily have meaning outside of numbers. The inspector code also doesn’t work with the generic version.

Anyway, here is a example of how it looks in the Inspector, and a wall of code after the break.

The editor code can be found on my Bitbucket repository.

using System;
using UnityEngine;
using Random = UnityEngine.Random;

public struct Range<T> : IEquatable<Range<T>>, IComparable<Range<T>> where T : struct, IEquatable<T>, IComparable<T> {
    public T min { get; set; }
    public T max { get; set; }

    public Range(T min, T max) : this() {
        if (min.CompareTo(max) <= 0) {
            this.min = min;
            this.max = max;
        } else {
            this.min = max;
            this.max = min;
        }
    }

    public bool valid {
        get { return min.CompareTo(max) <= 0; }
    }

    public Range<T> Validate() {
        return valid ? this : new Range<T> { min = max, max = min };
    }

    public bool Contains(T value) {
        return min.CompareTo(value) <= 0 && max.CompareTo(value) >= 0;
    }

    public bool Contains(Range<T> range) {
        return Contains(range.min) && Contains(range.max);
    }

    public bool Intersects(Range<T> range) {
        return Contains(range.min) && max.CompareTo(range.max) < 0
            || Contains(range.max) && min.CompareTo(range.min) > 0;
    }

    public Range<T> Apply(Func<T, T> function) {
        return new Range<T> {min = function(min), max = function(max)};
    }

    public int CompareTo(Range<T> range) {
        if (min.CompareTo(range.min) < 0 && max.CompareTo(range.max) < 0) return -1;
        if (min.CompareTo(range.min) > 0 && max.CompareTo(range.max) > 0) return 1;
        return 0;
    }

    public override bool Equals(object obj) {
        if (obj is Range<T>) {
            Range<T> range = (Range<T>) obj;

            return min.Equals(range.min) && max.Equals(range.max);
        }
        return false;
    }

    public override int GetHashCode() {
        return min.GetHashCode() ^ max.GetHashCode();
    }

    public bool Equals(Range<T> range) {
        return min.Equals(range.min) && max.Equals(range.max);
    }
}

[Serializable]
public struct FloatRange : IEquatable<FloatRange>, IComparable<FloatRange> {
    public float min;
    public float max;

    public FloatRange(float min, float max) : this() {
        this.min = Mathf.Min(min, max);
        this.max = Mathf.Max(min, max);
    }

    public float size { get { return max - min; } }
    public float random { get { return Random.Range(min, max); } }
    public bool valid { get { return min <= max; } }

    public FloatRange Validate() {
        return valid ? this : new FloatRange { min = max, max = min };
    }

    public bool Contains(float value) {
        return value >= min && value <= max;
    }

    public bool Contains(FloatRange range) {
        return Contains(min) && Contains(max);
    }

    public bool Intersects(FloatRange range) {
        return range.min < min && Contains(range.max)
            || range.max > max && Contains(range.min);
    }

    public FloatRange Apply(Func<float, float> function) {
        return new FloatRange { min = function(min), max = function(max)};
    }

    public float Lerp(float alpha) {
        if (alpha < 0) return min;
        if (alpha > 1) return max;

        return alpha*max + (1 - alpha)*min;
    }

    public float SmoothstepInterpolation(float alpha) {
        if(alpha < 0) return min;
        if(alpha > 1) return max;

        alpha = alpha * alpha * (3 - 2 * alpha);

        return alpha * max + (1 - alpha) * min;
    }

    public int CompareTo(FloatRange other) {
        if (this > other) return 1;
        if (this < other) return -1;
        return 0;
    }

    public override bool Equals(object obj) {
        if (obj is FloatRange) {
            FloatRange rf = (FloatRange) obj;
            return min.Equals(rf.min) && max.Equals(rf.max);
        }
        return false;
    }

    public override int GetHashCode() {
        return min.GetHashCode() ^ max.GetHashCode();
    }

    public override string ToString() {
        return string.Format("[{0}, {1}]", min, max);
    }

    public bool Equals(FloatRange other) {
        return min.Equals(other.min) && max.Equals(other.max);
    }

    public static bool operator ==(FloatRange a, FloatRange b) {
        return a.min.Equals(b.min) && a.max.Equals(b.max);
    }

    public static bool operator !=(FloatRange a, FloatRange b) {
        return !a.min.Equals(b.min) || !a.max.Equals(b.max);
    }

    public static bool operator <(FloatRange a, FloatRange b) {
        return a.min < b.min && a.max < b.max;
    }

    public static bool operator <=(FloatRange a, FloatRange b) {
        return a.min <= b.min && a.max <= b.max;
    }

    public static bool operator >(FloatRange a, FloatRange b) {
        return a.min > b.min && a.max > b.max;
    }

    public static bool operator >=(FloatRange a, FloatRange b) {
        return a.min >= b.min && a.max >= b.max;
    }

    public static FloatRange operator +(FloatRange a, FloatRange b) {
        return new FloatRange {min = a.min + b.min, max = a.max + b.max};
    }

    public static FloatRange operator +(FloatRange a, float b) {
        return new FloatRange { min = a.min + b, max = a.max + b };
    }

    public static FloatRange operator +(float a, FloatRange b) {
        return new FloatRange { min = b.min + a, max = b.max + a };
    }

    public static FloatRange operator -(FloatRange a, FloatRange b) {
        return new FloatRange {min = a.min - b.min, max = a.max - b.max};
    }

    public static FloatRange operator -(FloatRange a, float b) {
        return new FloatRange { min = a.min - b, max = a.max - b };
    }

    public static FloatRange operator -(float a, FloatRange b) {
        return new FloatRange { min = b.min - a, max = b.max - a };
    }

    public static FloatRange operator -(FloatRange a) {
        return new FloatRange { min = -a.max, max = -a.min };
    }

    public static FloatRange operator *(FloatRange a, FloatRange b) {
        return new FloatRange {min = a.min*b.min, max = a.max * b.max };
    }

    public static FloatRange operator *(FloatRange a, float b) {
        return new FloatRange { min = a.min * b, max = a.max * b };
    }

    public static FloatRange operator *(float a, FloatRange b) {
        return new FloatRange { min = b.min * a, max = b.max * a };
    }

    public static FloatRange operator /(FloatRange a, FloatRange b) {
        return new FloatRange {min = a.min / b.min, max = a.max / b.max};
    }

    public static FloatRange operator /(FloatRange a, float b) {
        return new FloatRange { min = a.min / b, max = a.max / b };
    }

    public static explicit operator IntRange(FloatRange value) {
        return new IntRange { min = (int)value.min, max = (int)value.max };
    }
}

[Serializable]
public struct IntRange : IEquatable<IntRange>, IComparable<IntRange> {
    public int min;
    public int max;

    public IntRange(int min, int max) : this() {
        this.min = Mathf.Min(min, max);
        this.max = Mathf.Max(min, max);
    }

    public float size { get { return max - min; } }
    public int random { get { return Random.Range(min, max + 1); } }
    public bool valid { get { return min <= max; } }

    public IntRange Validate() {
        return valid ? this : new IntRange {min = max, max = min};
    }

    public bool Contains(int value) {
        return value >= min && value <= max;
    }

    public bool Contains(IntRange range) {
        return Contains(min) && Contains(max);
    }

    public bool Intersects(IntRange range) {
        return range.min < min && Contains(range.max)
            || range.max > max && Contains(range.min);
    }

    public IntRange Apply(Func<int, int> function) {
        return new IntRange { min = function(min), max = function(max) };
    }

    public float Lerp(float alpha) {
        if(alpha < 0) return min;
        if(alpha > 1) return max;

        return alpha * max + (1 - alpha) * min;
    }

    public int LerpToInt(float alpha) {
        if(alpha < 0) return min;
        if(alpha > 1) return max;

        return Mathf.RoundToInt(alpha * max + (1 - alpha) * min);
    }

    public float SmoothstepInterpolation(float alpha) {
        if(alpha < 0) return min;
        if(alpha > 1) return max;

        alpha = alpha * alpha * (3 - 2 * alpha);

        return alpha * max + (1 - alpha) * min;
    }

    public float SmoothstepInterpolationToInt(float alpha) {
        if(alpha < 0) return min;
        if(alpha > 1) return max;

        alpha = alpha * alpha * (3 - 2 * alpha);

        return Mathf.RoundToInt(alpha * max + (1 - alpha) * min);
    }

    public int CompareTo(IntRange other) {
        if(this > other) return 1;
        if(this < other) return -1;
        return 0;
    }

    public override bool Equals(object obj) {
        if(obj is IntRange) {
            IntRange rf = (IntRange)obj;
            return min.Equals(rf.min) && max.Equals(rf.max);
        }
        return false;
    }

    public override int GetHashCode() {
        return min.GetHashCode() ^ max.GetHashCode();
    }

    public override string ToString() {
        return string.Format("[{0}, {1}]", min, max);
    }

    public bool Equals(IntRange other) {
        return min.Equals(other.min) && max.Equals(other.max);
    }

    public static bool operator ==(IntRange a, IntRange b) {
        return a.min.Equals(b.min) && a.max.Equals(b.max);
    }

    public static bool operator !=(IntRange a, IntRange b) {
        return !a.min.Equals(b.min) || !a.max.Equals(b.max);
    }

    public static bool operator <(IntRange a, IntRange b) {
        return a.min < b.min && a.max < b.max;
    }

    public static bool operator <=(IntRange a, IntRange b) {
        return a.min <= b.min && a.max <= b.max;
    }

    public static bool operator >(IntRange a, IntRange b) {
        return a.min > b.min && a.max > b.max;
    }

    public static bool operator >=(IntRange a, IntRange b) {
        return a.min >= b.min && a.max >= b.max;
    }

    public static IntRange operator +(IntRange a, IntRange b) {
        return new IntRange { min = a.min + b.min, max = a.max + b.max };
    }

    public static IntRange operator +(IntRange a, int b) {
        return new IntRange {min = a.min + b, max = a.max + b};
    }

    public static IntRange operator +(int a, IntRange b) {
        return new IntRange { min = b.min + a, max = b.max + a };
    }

    public static IntRange operator -(IntRange a, IntRange b) {
        return new IntRange { min = a.min - b.min, max = a.max - b.max };
    }

    public static IntRange operator -(IntRange a, int b) {
        return new IntRange { min = a.min - b, max = a.max - b };
    }

    public static IntRange operator -(int a, IntRange b) {
        return new IntRange { min = b.min - a, max = b.max - a };
    }

    public static IntRange operator -(IntRange a) {
        return new IntRange {min = -a.max, max = -a.min};
    }

    public static IntRange operator *(IntRange a, IntRange b) {
        return new IntRange { min = a.min * b.min, max = a.max * b.max };
    }

    public static IntRange operator *(IntRange a, int b) {
        return new IntRange { min = a.min * b, max = a.max * b };
    }

    public static IntRange operator *(int a, IntRange b) {
        return new IntRange { min = b.min * a, max = b.max * a };
    }

    public static IntRange operator /(IntRange a, IntRange b) {
        return new IntRange { min = a.min / b.min, max = a.max / b.max };
    }

    public static IntRange operator /(IntRange a, int b) {
        return new IntRange { min = a.min / b, max = a.max / b };
    }

    public static implicit operator FloatRange(IntRange value) {
        return new FloatRange { min = value.min, max = value.max };
    }
}

public class MinMaxRangeAttribute : Attribute {
    public MinMaxRangeAttribute(float min, float max) {
        this.min = min;
        this.max = max;
    }
    public float min { get; private set; }
    public float max { get; private set; }
}

Comments

Your email address will not be published. Required fields are marked *