diff options
Diffstat (limited to 'source/core/StarInterpolation.hpp')
-rw-r--r-- | source/core/StarInterpolation.hpp | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/source/core/StarInterpolation.hpp b/source/core/StarInterpolation.hpp new file mode 100644 index 0000000..1797495 --- /dev/null +++ b/source/core/StarInterpolation.hpp @@ -0,0 +1,454 @@ +#ifndef STAR_INTERPOLATE_BASE +#define STAR_INTERPOLATE_BASE + +#include "StarMathCommon.hpp" +#include "StarArray.hpp" +#include "StarAlgorithm.hpp" + +namespace Star { + +enum class BoundMode { + Clamp, + Extrapolate, + Wrap +}; + +enum class InterpolationMode { + HalfStep, + Linear, + Cubic +}; + +template <typename T1, typename T2> +T2 angleLerp(T1 const& offset, T2 const& f0, T2 const& f1) { + return f0 + angleDiff(f0, f1) * offset; +} + +template <typename T1, typename T2> +T2 sinEase(T1 const& offset, T2 const& f0, T2 const& f1) { + T1 w = (sin(offset * Constants::pi - Constants::pi / 2) + 1) / 2; + return f0 * (1 - w) + f1 * w; +} + +template <typename T1, typename T2> +T2 lerp(T1 const& offset, T2 const& f0, T2 const& f1) { + return f0 * (1 - offset) + f1 * (offset); +} + +template <typename T1, typename T2> +T2 lerpWithLimit(Maybe<T2> const& limit, T1 const& offset, T2 const& f0, T2 const& f1) { + if (limit && abs(f1 - f0) > *limit) + return f1; + return lerp(offset, f0, f1); +} + +template <typename T1, typename T2> +T2 step(T1 threshold, T1 x, T2 a, T2 b) { + if (x < threshold) + return a; + else + return b; +} + +template <typename T1, typename T2> +T2 halfStep(T1 x, T2 a, T2 b) { + if (x < 0.5) + return a; + else + return b; +} + +template <typename T1, typename T2> +T2 cubic4(T1 const& x, T2 const& f0, T2 const& f1, T2 const& f2, T2 const& f3) { + // (-1/2 * f0 + 3/2 * f1 + -3/2 * f2 + 1/2 * f3) * x * x * x + + // ( 1 * f0 + -5/2 * f1 + 2 * f2 + -1/2 * f3) * x * x + + // (-1/2 * f0 + 0 * f1 + 1/2 * f2 + 0 * f3) * x + + // ( 0 * f0 + 1 * f1 + 0 * f2 + 0 * f3) * 1.0 + return f1 + (f2 - f0 + (f0 * 2.0 - f1 * 5.0 + f2 * 4.0 - f3 + ((f1 - f2) * 3.0 + f3 - f0) * x) * x) * x * 0.5; +} + +template <typename T1, typename T2> +T2 catmulRom4(T1 const& x, T2 const& f0, T2 const& f1, T2 const& f2, T2 const& f3) { + return ((f1 * 2) + (-f0 + f2) * x + (f0 * 2 - f1 * 5 + f2 * 4 - f3) * x * x + + (-f0 + f1 * 3 - f2 * 3 + f3) * x * x * x) + * 0.5; +} + +template <typename T1, typename T2> +T2 hermite2(T1 const& x, T2 const& a, T2 const& b) { + return a + (b - a) * x * x * (3 - 2 * x); +} + +template <typename T1, typename T2> +T2 quintic2(T1 const& x, T2 const& a, T2 const& b) { + return a + (b - a) * x * x * x * (x * (x * 6 - 15) + 10); +} + +template <typename WeightT> +struct LinearWeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 2> WeightVec; + + WeightVec operator()(Weight x) const { + return {1 - x, x}; + } +}; + +template <typename WeightT> +struct StepWeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 2> WeightVec; + + StepWeightOperator(Weight threshold = 0.5) : threshold(threshold) {} + + WeightVec operator()(Weight x) const { + if (x < threshold) + return {1, 0}; + else + return {0, 1}; + } + + Weight threshold; +}; + +template <typename WeightT> +struct SinWeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 2> WeightVec; + + WeightVec operator()(Weight x) const { + Weight w = (sin(x * Constants::pi - Constants::pi / 2) + 1) / 2; + return {1 - w, w}; + } +}; + +template <typename WeightT> +struct Hermite2WeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 2> WeightVec; + + WeightVec operator()(Weight x) const { + Weight w = x * x * (3 - 2 * x); + return {1 - w, w}; + } +}; + +template <typename WeightT> +struct Quintic2WeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 2> WeightVec; + + WeightVec operator()(Weight x) const { + Weight w = x * x * x * (x * (x * 6 - 15) + 10); + return {1 - w, w}; + } +}; + +// Setting 'LinearExtrapolate' flag to true changes the weights to be linear +// when x is outside of the range [0.0, 1.0] +template <typename WeightT> +struct Cubic4WeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 4> WeightVec; + + Cubic4WeightOperator(bool le = false) : linearExtrapolate(le) {} + + WeightVec operator()(Weight x) const { + if (linearExtrapolate && x > 1) { + return {0, 0, 2 - x, x - 1}; + } else if (linearExtrapolate && x < 0) { + return {-x, 1 + x, 0, 0}; + } else { + // (-1/2 * f0 + 3/2 * f1 + -3/2 * f2 + 1/2 * f3) * x*x*x + + // ( 1 * f0 + -5/2 * f1 + 2 * f2 + -1/2 * f3) * x*x + + // (-1/2 * f0 + 0 * f1 + 1/2 * f2 + 0 * f3) * x + + // ( 0 * f0 + 1 * f1 + 0 * f2 + 0 * f3) * 1.0 + + Weight x2 = x * x; + Weight x3 = x2 * x; + return WeightVec(-0.5 * x3 + 1 * x2 - 0.5 * x, + 1.5 * x3 + -2.5 * x2 + 1.0, + -1.5 * x3 + 2.0 * x2 + 0.5 * x, + 0.5 * x3 - 0.5 * x2); + } + } + bool linearExtrapolate; +}; + +// Setting 'LinearExtrapolate' flag to true changes the weights to be linear +// when x is outside of the range [0.0, 1.0] +template <typename WeightT> +struct Catmul4WeightOperator { + typedef WeightT Weight; + typedef Array<Weight, 4> WeightVec; + + Catmul4WeightOperator(bool le = false) : linearExtrapolate(le) {} + + WeightVec operator()(Weight x) const { + if (linearExtrapolate && x > 1) { + return {0, 0, 2 - x, x - 1}; + } else if (linearExtrapolate && x < 0) { + return {-x, 1 + x, 0, 0}; + } else { + Weight x2 = x * x; + Weight x3 = x * x * x; + return {(-x3 + x2 * 2 - x) / 2, (x3 * 3 - x2 * 5 + 2) / 2, (-x3 * 3 + x2 * 4 + x) / 2, (x3 - x2) / 2}; + } + } + + bool linearExtrapolate; +}; + +template <typename Loctype, typename IndexType> +struct Bound2 { + IndexType i0; + IndexType i1; + Loctype offset; +}; + +// loc should be in "index space", meaning that 0 points exactly to the first +// element and extent - 1 +// points exactly to the last element. +template <typename LocType, typename IndexType> +Bound2<LocType, IndexType> getBound2(LocType loc, IndexType extent, BoundMode bmode) { + Bound2<LocType, IndexType> bound; + if (extent <= 1) { + bound.i0 = bound.i1 = bound.offset = 0; + return bound; + } + + bound.offset = 0; + if (bmode == BoundMode::Wrap) { + loc = pfmod<LocType>(loc, extent); + } else { + LocType newLoc = clamp<LocType>(loc, 0, extent - 1); + if (bmode == BoundMode::Extrapolate) + bound.offset += loc - newLoc; + + loc = newLoc; + } + + bound.i0 = IndexType(loc); + + if (bound.i0 == extent - 1) { + if (bmode == BoundMode::Wrap) { + bound.i1 = 0; + } else { + bound.i1 = bound.i0; + bound.i0 -= 1; + } + } else { + bound.i1 = bound.i0 + 1; + } + + bound.offset += loc - bound.i0; + + return bound; +} + +template <typename Loctype, typename IndexType> +struct Bound4 { + Bound4() {} + IndexType i0; + IndexType i1; + IndexType i2; + IndexType i3; + Loctype offset; +}; + +// loc should be in "index space", meaning that 0 points exactly to the first +// element and extent - 1 +// points exactly to the last element. +template <typename LocType, typename IndexType> +Bound4<LocType, IndexType> getBound4(LocType loc, IndexType extent, BoundMode bmode) { + Bound4<LocType, IndexType> bound; + if (extent <= 1) { + bound.i0 = bound.i1 = bound.i2 = bound.i3 = bound.offset = 0; + return bound; + } + + bound.offset = 0; + if (bmode == BoundMode::Wrap) { + loc = pfmod<LocType>(loc, extent); + } else { + LocType newLoc = clamp<LocType>(loc, 0, extent - 1); + if (bmode == BoundMode::Extrapolate) + bound.offset += loc - newLoc; + + loc = newLoc; + } + + bound.i1 = IndexType(loc); + + if (bound.i1 == extent - 1) { + if (bmode == BoundMode::Wrap) { + bound.i0 = bound.i1 - 1; + bound.i2 = 0; + bound.i3 = 1; + } else { + bound.i1 = bound.i1 - 2; + + bound.i0 = bound.i1 - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = bound.i2 + 1; + } + } else if (bound.i1 == extent - 2) { + if (bmode == BoundMode::Wrap) { + bound.i0 = bound.i1 - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = 0; + } else { + bound.i1 = bound.i1 - 1; + + bound.i0 = bound.i1 - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = bound.i2 + 1; + } + } else if (bound.i1 == 0) { + if (bmode == BoundMode::Wrap) { + bound.i0 = extent - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = bound.i2 + 1; + } else { + bound.i1 = bound.i1 + 1; + + bound.i0 = bound.i1 - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = bound.i2 + 1; + } + } else { + bound.i0 = bound.i1 - 1; + bound.i2 = bound.i1 + 1; + bound.i3 = bound.i1 + 2; + } + + bound.offset += loc - bound.i1; + + return bound; +} + +template <typename Container, typename Pos, typename WeightOp> +typename Container::value_type listInterpolate2( + Container const& cont, Pos x, WeightOp weightOp, BoundMode bmode = BoundMode::Clamp) { + if (cont.size() == 0) { + return typename Container::value_type(); + } else if (cont.size() == 1) { + return cont[0]; + } else { + auto bound = getBound2(x, cont.size(), bmode); + auto weights = weightOp(bound.offset); + return cont[bound.i0] * weights[0] + cont[bound.i1] * weights[1]; + } +} + +template <typename Container, typename Pos, typename WeightOp> +typename Container::value_type listInterpolate4( + Container const& cont, Pos x, WeightOp weightOp, BoundMode bmode = BoundMode::Clamp) { + if (cont.size() == 0) { + return typename Container::value_type(); + } else if (cont.size() == 1) { + return cont[0]; + } else { + auto bound = getBound4(x, cont.size(), bmode); + auto weights = weightOp(bound.offset); + return cont[bound.i0] * weights[0] + cont[bound.i1] * weights[1] + cont[bound.i2] * weights[2] + + cont[bound.i3] * weights[3]; + } +} + +// Returns an index value (not integer) that represents the value that, if +// passed in as an index to a simple linear interpolation of the given +// container, would yield the given value. (In other words, this goes from +// function space to index space on a list of points). Useful for doing +// interpolation on functions that are unevenly spaced. Given container must +// be sorted. If there is an ambiguity on points due to repeat points, will +// choose the lower-most of the points. +template <typename Iterator, typename Pos, typename Comp, typename PosGetter> +Pos inverseLinearInterpolateLower(Iterator begin, Iterator end, Pos t, Comp&& comp, PosGetter&& posGetter) { + // Container must be at least size 2 for this to make sense. + if (begin == end || std::next(begin) == end) + return Pos(); + + Iterator i = std::lower_bound(std::next(begin), std::prev(end), t, forward<Comp>(comp)); + + --i; + Pos min = posGetter(*i); + Pos max = posGetter(*(++i)); + Pos ipos = Pos(std::distance(begin, --i)); + + Pos dist = max - min; + if (dist == 0) + return ipos; + else + return ipos + (t - min) / dist; +} + +template <typename Iterator, typename Pos> +Pos inverseLinearInterpolateLower(Iterator begin, Iterator end, Pos t) { + return inverseLinearInterpolateLower(begin, end, t, std::less<Pos>(), identity()); +} + +// Same as inverseLinearInterpolateLower, except chooses the upper most of the +// points in the ambiguous case. +template <typename Iterator, typename Pos, typename Comp, typename PosGetter> +Pos inverseLinearInterpolateUpper(Iterator begin, Iterator end, Pos t, Comp&& comp, PosGetter&& posGetter) { + // Container must be at least size 2 for this to make sense. + if (begin == end || std::next(begin) == end) + return Pos(); + + Iterator i = std::upper_bound(std::next(begin), std::prev(end), t, forward<Comp>(comp)); + + --i; + Pos min = posGetter(*i); + Pos max = posGetter(*(++i)); + Pos ipos = Pos(std::distance(begin, --i)); + + Pos dist = max - min; + if (dist == 0) + return ipos + 1; + else + return ipos + (t - min) / dist; +} + +template <typename Iterator, typename Pos> +Pos inverseLinearInterpolateUpper(Iterator begin, Iterator end, Pos t) { + return inverseLinearInterpolateUpper(begin, end, t, std::less<Pos>(), identity()); +} + +template <typename XContainer, typename YContainer, typename PositionType, typename WeightOp> +typename YContainer::value_type parametricInterpolate2(XContainer const& xvals, + YContainer const& yvals, + PositionType const& position, + WeightOp weightOp, + BoundMode bmode) { + starAssert(xvals.size() != 0); + starAssert(xvals.size() == yvals.size()); + + if (yvals.size() == 1) + return yvals[0]; + + PositionType ipos = inverseLinearInterpolateLower(xvals.begin(), xvals.end(), position); + + return listInterpolate2(yvals, ipos, weightOp, bmode); +} + +template <typename XContainer, typename YContainer, typename PositionType, typename WeightOp> +typename YContainer::value_type parametricInterpolate4(XContainer const& xvals, + YContainer const& yvals, + PositionType const& position, + WeightOp weightOp, + BoundMode bmode) { + starAssert(xvals.size() != 0); + starAssert(xvals.size() == yvals.size()); + + if (yvals.size() == 1) + return yvals[0]; + + PositionType ipos = inverseLinearInterpolateLower(xvals.begin(), xvals.end(), position); + + return listInterpolate4(yvals, ipos, weightOp, bmode); +} + +} + +#endif |