I think these work. I trip over this every time I try to do it; this is my most recent code and I think a lot of stuff for me wouldn't work if these were wrong but... I think these work.
#ifndef UTIL_CLERP_H #define UTIL_CLERP_H // circular linear interpolation; note: these are all default mod 1.0, not M_2PI float clerp_direction (float H0, float H1, float Domain); float clerp_fmod (float H, float Mod); float clerp_dist (float H0, float H1, float Domain = 1.0); float clerp_do (float H0, float H1, float Alpha, float Domain = 1.0); float clerp_closest (float HSrc, float HDst0, float HDst1, float Alpha); float clerp_max_do (float H0, float H1, float Max, float Domain); // integer version int clerp_mod (int H, int Mod); // modulo x along a circular domain, so that -1 -> Radix - 1 inline int zmod (int X, int Radix) { if (X < 0) return Radix - ((-X - 1) % Radix) - 1; return X % Radix; } #endif // CLERP_H
#include "clerp.h" #include <math.h> #include <algorithm> using namespace std; float clerp_fmod (float H, float Mod) { if (H >= 0) return fmod( H, Mod ); return Mod - fmod (-H, Mod); }; int clerp_mod (int H, int Mod) { int HM = H % Mod; if (HM < 0) HM = Mod + HM; return HM; } float clerp_direction (float H0, float H1, float Domain) { H0 = fmod (H0, Domain); H1 = fmod (H1, Domain); float Factor = 1.0f; if (H0 > H1) { float Temp = H1; H1 = H0; H0 = Temp; Factor = Factor * -1.0f; } float D1 = fabs (H1 - H0); float D2 = fabs (H0 + Domain - H1); if (D1 > D2) { Factor = Factor * -1.0f; } return Factor; } float clerp_dist (float H0, float H1, float Domain) { // we can either rotate H0 to the left, or to the right H0 = clerp_fmod (H0, Domain); H1 = clerp_fmod (H1, Domain); float Dist = 0.0; if (H0 < H1) { float LeftDist = H0 + (Domain - H1); float RightDist = H1 - H0; Dist = min (LeftDist, RightDist); } else { float LeftDist = H1 + (Domain - H0); float RightDist = H0 - H1; Dist = min (LeftDist, RightDist); } return Dist; } float clerp_do (float H0, float H1, float Alpha, float Domain) { // which is the closest direction to rotate towards? if it's the other way, then bump it H0 = clerp_fmod (H0, Domain); H1 = clerp_fmod (H1, Domain); if (fabs(H0 - H1) > fabs(H0 + Domain - H1)) { H0 += Domain; } else if (fabs (H1 - H0) > fabs (H1 + Domain - H0)) { H1 += Domain; } float HOut = H0 * (1.0 - Alpha) + H1 * Alpha; clerp_fmod (HOut, Domain); return HOut; }; float clerp_closest (float HSrc, float HDst0, float HDst1, float Alpha) { if( clerp_dist( HSrc, HDst0 ) < clerp_dist( HSrc, HDst1 ) ) { return clerp_do( HSrc, HDst0, Alpha ); } else { return clerp_do( HSrc, HDst1, Alpha ); } }; // // move H0 to H1 up to Max amount, on Domain // float clerp_max_do (float H0, float H1, float Max, float Domain) { // which is the closest direction to rotate towards? if it's the other way, then bump it H0 = clerp_fmod (H0, Domain); H1 = clerp_fmod (H1, Domain); if (fabs (H0 - H1) > fabs (H0 + Domain - H1)) { H0 = H0 + Domain; } else if (fabs (H1 - H0) > fabs (H1 + Domain - H0)) { H1 = H1 + Domain; } float Delta = H1 - H0; if (Delta > Max) Delta = Max; else if (Delta < (Max * -1.0f)) Delta = Max * -1.0f; float HOut = H0 + Delta; HOut = clerp_fmod (HOut, Domain); return HOut; }