1
\$\begingroup\$

This is an extension of my Attributes system I wrote about in these questions:

It is unrelated to the core functionality under review in those questions, but I present them for reference.

I wrote a banded interpolator. This takes a set of input ranges mapped to output values and, for an input value between any of the input ranges, returns a value interpolated between the two values of the bands the input value is between.

Eg.

Bands: [ 0:1, 10:2, 100:3 ], input: 0.5, linear interpolation.

The input is between 0 and 10, so the output is the linear interpolation of 1 and 2, using delta value: \$\frac{0.5}{10-0} = 0.05\$ Therefore the result is: \$1 + 0.05(2-1) = 1.05\$

What's special about this implementation is that it works for any type of band key, any type of band value, and any method of interpolation. This is of particular use for me, since in game development I find myself needing to interpolate colours, vectors, etc. on top of the standard data types.

Implementation

IInterpolator

/// <summary>
/// Interface for interpolators.
/// </summary>
/// <typeparam name="TResult">The type of value to interpolate.</typeparam>
/// <typeparam name="TDelta">The type of the delta value.</typeparam>
public interface IInterpolator<TResult, TDelta>
{
    /// <summary>
    /// Interpolates between from and to by delta.
    /// </summary>
    TResult Interpolate(TResult from, TResult to, TDelta delta);
}

BandedInterpolatedAttribute

This is the real meat and potatoes. For reference, the Operator class is from the MiscUtils library, and allows you to perform generic operations such as addition and subtraction on any type, provided that type supports the required operator.

public class BandedInterpolatedAttribute<TKey, TResult> : IAttribute<TResult>
{
    private IInterpolator<TResult, TKey> interpolator;
    private Func<TKey> deltaGetter;
    private IDictionary<TKey, TResult> bands;

    public BandedInterpolatedAttribute(Func<TKey> deltaGetter, IDictionary<TKey, TResult> bands, IInterpolator<TResult, TKey> interpolator)
    {
        if (deltaGetter == null)
        {
            throw new ArgumentNullException("deltaGetter");
        }

        if (bands == null)
        {
            throw new ArgumentNullException("bands");
        }

        if (interpolator == null)
        {
            throw new ArgumentNullException("interpolator");
        }

        this.bands = bands;
        this.deltaGetter = deltaGetter;
        this.interpolator = interpolator;
    }

    public TResult Value
    {
        get
        {
            var orderedKeys = bands.Keys.OrderBy(x => x);
            var lowestKey = orderedKeys.First();
            var highestKey = orderedKeys.Last();

            var delta = deltaGetter();

            if (Operator<TKey>.LessThanOrEqual(delta, lowestKey))
            {
                return bands[lowestKey];
            }
            else if (Operator<TKey>.GreaterThanOrEqual(delta, highestKey))
            {
                return bands[highestKey];
            }
            else
            {
                var lowBand = orderedKeys.Last(x => Operator<TKey>.LessThan(x, delta));
                var highBand = orderedKeys.First(x => Operator<TKey>.GreaterThan(x, delta));

                var normalizedDelta = Operator<TKey>.Divide(Operator<TKey>.Subtract(delta, lowBand), Operator<TKey>.Subtract(highBand, lowBand));

                return interpolator.Interpolate(bands[lowBand], bands[highBand], normalizedDelta);
            }
        }
    }
}

Usage

var randomGenerator = new System.Random();

var interpolatedAttribute = new BandedInterpolatedAttribute<double, Vector3>(
    () => randomGenerator.NextDouble(), 
    new Dictionary<double, Vector3>()
    {
        {0d, new Vector3(0,0,0)},
        {1d, new Vector3(10,10,10)}
    }, 
    new LinearInterpolator<Vector3, double>());

for (var i = 0; i < 10; i++)
{
    Console.WriteLine(interpolatedAttribute.Value);
}
\$\endgroup\$
3
\$\begingroup\$
  • I would not use a property for Value; it should be a method. Properties should be fast, and generally should not change value each time you call the get method.
  • Value.get is redundantly sorting bands on every call.
  • The interpolator might be a function instead (delegate).
  • Good job!
\$\endgroup\$
1
  • \$\begingroup\$ I agree with Value, it's mainly done that way so it fits in to the Attributes system, but perhaps an Attribute wrapper would be a better bet. Thanks for the comments! \$\endgroup\$
    – Nick Udell
    Jan 30 '15 at 15:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.