# A banded interpolator, for any type and any interpolation method

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);
}