Merge branch 'foo'
[faros-demo] / src / track.cc
diff --git a/src/track.cc b/src/track.cc
new file mode 100644 (file)
index 0000000..bc775ba
--- /dev/null
@@ -0,0 +1,146 @@
+#include <assert.h>
+#include <algorithm>
+#include "track.h"
+
+static long remap_time(ExtrapMode mode, long t, long t0, long t1);
+static float smoothstep(float a, float b, float x);
+
+Track::Track()
+{
+       interp = INTERP_SIGMOID;
+       extrap = EXTRAP_CLAMP;
+       keys_sorted = true;
+       defval = 0.0f;
+}
+
+void Track::clear()
+{
+       keys.clear();
+}
+
+void Track::set_key(long tm, float val)
+{
+       int idx = find_key(tm);
+       if(idx >= 0) {
+               keys[idx].value = val;
+               return;
+       }
+
+       TrackKey key = {tm, val};
+       keys.push_back(key);
+       keys_sorted = false;
+}
+
+float Track::get_key(long tm) const
+{
+       int idx = find_key(tm);
+       if(idx == -1) return 0.0f;
+       return keys[idx].value;
+}
+
+int Track::find_key(long tm) const
+{
+       int sz = (int)keys.size();
+       for(int i=0; i<sz; i++) {
+               if(keys[i].time == tm) return i;
+       }
+       return -1;
+}
+
+static bool keycmp(const TrackKey &a, const TrackKey &b)
+{
+       return a.time < b.time;
+}
+
+float Track::operator ()(long tm) const
+{
+       if(keys.empty()) {
+               return defval;
+       }
+
+       int nkeys = keys.size();
+       if(nkeys == 1) {
+               return keys[0].value;
+       }
+       if(!keys_sorted) {
+               Track *track = (Track*)this;
+               std::sort(track->keys.begin(), track->keys.end(), keycmp);
+               keys_sorted = true;
+       }
+
+       long tstart = keys[0].time;
+       long tend = keys[nkeys - 1].time;
+
+       tm = remap_time(extrap, tm, tstart, tend);
+
+       int idx0 = get_interval(tm);
+       assert(idx0 >= 0 && idx0 < nkeys);
+       int idx1 = idx0 + 1;
+
+       if(idx0 == nkeys - 1) {
+               return keys[idx0].value;
+       }
+
+       float dt = (float)(keys[idx1].time - keys[idx0].time);
+       float t = (float)(tm - keys[idx0].time) / dt;
+
+       switch(interp) {
+       case INTERP_STEP:
+               return keys[idx0].value;
+
+       case INTERP_SIGMOID:
+               t = smoothstep(0, 1, t);
+       case INTERP_LINEAR:
+               return keys[idx0].value + (keys[idx1].value - keys[idx0].value) * t;
+
+       default:
+               break;
+       }
+       return 0.0f;
+}
+
+int Track::get_interval(long tm) const
+{
+       int nkeys = (int)keys.size();
+
+       for(int i=0; i<nkeys-1; i++) {
+               if(tm < keys[i + 1].time) {
+                       return i;
+               }
+       }
+       return nkeys - 1;
+}
+
+static long remap_time(ExtrapMode mode, long t, long t0, long t1)
+{
+       long interval = t1 - t0;
+
+       switch(mode) {
+       case EXTRAP_CLAMP:
+               if(interval <= 0) return t0;
+               return t < t0 ? t0 : (t >= t1 ? t1 : t);
+
+       case EXTRAP_REPEAT:
+               if(interval > 0) {
+                       long x = (t - t0) % interval;
+                       if(x < 0) x += interval;
+                       return x + t0;
+               }
+               return t0;
+
+       default:
+               break;
+       }
+
+       assert(!"unreachable");
+       return t;
+}
+
+static float smoothstep(float a, float b, float x)
+{
+       if(x < a) return 0.0f;
+       if(x >= b) return 1.0f;
+
+       x = (x - a) / (b - a);
+       return x * x * (3.0f - 2.0f * x);
+}