package com.bergerkiller.bukkit.common.collections;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import com.bergerkiller.bukkit.common.utils.MathUtil;
/**
* Uses a Double-Double sorted map system to linearly interpolate between values.
* For example, if there are two entries put in this map:<br>
* - (key = 2.0; value = 5.0)<br>
* - (key = 3.0; value = 12.0)<br>
* Performing get(2.5) would result in a value of 8.5 to be returned.<br><br>
*
* If a key is out of bounds (lower than the minimum, higher than the maximum) the value is clamped.
* Performing get(4.0) would result in a value of 12.0 to be returned.<br><br>
*
* No get operations should be performed on an empty map, the behaviour is unspecified.
*/
public class InterpolatedMap {
private final List<Entry> entries = new ArrayList<Entry>();
/**
* Checks whether this Interpolated Map is empty or not
*
* @return True if the map is empty, False if not
*/
public boolean isEmpty() {
return entries.isEmpty();
}
/**
* Performs linear interpolation logic to obtain the value at a key
*
* @param key to get the interpolated value of
* @return value
*/
public double get(double key) {
ListIterator<Entry> listiter = entries.listIterator();
Entry entry = null;
while (listiter.hasNext()) {
entry = listiter.next();
if (entry.key == key) {
// Exact match, no need to interpolate
return entry.value;
} else if (entry.key > key) {
// This is the first time the entry key is larger
// Obtain the previous key if available, otherwise we clamp at this first value
if (listiter.hasPrevious()) {
// We have a previous (lower key) value, interpolate
final Entry previous = listiter.previous();
// Obtain a stage in the interpolation (value between 0 and 1)
final double stage = (key - previous.key) / (entry.key - previous.key);
// Interpolate
return MathUtil.lerp(previous.value, entry.value, stage);
} else {
// No previous value, we are at the start, clamp
return entry.value;
}
}
}
// We went through all entries, but could not find an entry that was larger
// Depending on whether the list was empty we return the maximum value, or a constant
if (entry == null) {
// Map was empty, return a constant
return 0.0;
} else {
// This is the last entry and also the largest
return entry.value;
}
}
/**
* Clears this Interpolated Map
*/
public void clear() {
entries.clear();
}
/**
* Puts a single entry into this Interpolated Map
*
* @param key of the entry
* @param value of the entry
*/
public void put(double key, double value) {
ListIterator<Entry> listiter = entries.listIterator();
Entry newEntry = new Entry(key, value);
// Deal with an empty list in a quick manner
if (entries.isEmpty()) {
entries.add(newEntry);
return;
}
// Check if the new entry should be put before the very first element
while (listiter.hasNext()) {
final Entry entry = listiter.next();
if (key < entry.key) {
// The key is now greater than the previously ticked element
// Set a new element at the index
if (listiter.hasPrevious()) {
listiter.previous();
listiter.add(newEntry);
} else {
entries.add(0, newEntry);
}
return;
} else if (key == entry.key) {
listiter.set(newEntry);
return;
}
}
// Nothing was added this run, add a new entry at the end
entries.add(newEntry);
}
private static class Entry {
public final double key, value;
public Entry(double key, double value) {
this.key = key;
this.value = value;
}
}
}