1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
#pragma once
#include "chrono.hh"
#include "util.hh"
#include <cmath>
#include <stdexcept>
/// class for simple/linear animation/transition of values
class AnimValue {
public:
/// constructor
AnimValue(double value = 0.0, double rate = 1.0): m_value(value), m_target(value), m_rate(rate), m_time(Clock::now()) {}
/// move animation forward by diff
void move(double diff) { m_value += diff; }
/// gets animition target
double getTarget() const { return m_target; }
/// sets animation target (end point)
void setTarget(double target, bool step = false) { m_target = target; if (step) m_value = target; }
/// sets animation range
void setRange(double tmin, double tmax) {
if (tmin > tmax) throw std::logic_error("AnimValue range is reversed");
m_target = clamp(m_target, tmin, tmax);
}
/// hard-sets anim value
void setValue(double value) { m_value = value; }
/// set the adjustment rate
void setRate(double value) { m_rate = value; }
/// get current anim value
double get() const {
double maxadj = m_rate * duration();
double diff = m_value - m_target;
double adj = std::min(maxadj, std::fabs(diff));
if (diff > 0.0) m_value -= adj; else m_value += adj;
return m_value;
}
private:
double duration() const {
auto newtime = Clock::now();
Seconds t = newtime - m_time;
m_time = newtime;
return clamp(t.count());
}
mutable double m_value;
double m_target;
double m_rate;
mutable Time m_time;
};
/// easing animations
class AnimAcceleration {
public:
/// constructor
AnimAcceleration(): m_target(), m_songs(), m_position(), m_velocity(), m_marginLeft(), m_marginRight(), m_time() {}
/// set margins
void setMargins(double left, double right) { m_marginLeft = left; m_marginRight = right; }
/// get current value
double getValue() {
const double acceleration = 50.0; // the coefficient of velocity changes (animation speed)
const double overshoot = 0.95; // Over 1.0 decelerates too late, less than 1.0 decelerates too early
if (m_songs == 0) return m_target;
double num = m_marginLeft + m_songs + m_marginRight;
auto curtime = Clock::now();
double duration = Seconds(curtime - m_time).count();
m_time = curtime;
if (!(duration > 0.0)) return m_position; // Negative value or NaN, or no songs - skip processing
if (duration > 1.0) {
// More than one second has elapsed, assume that we have stopped on target already
m_velocity = 0.0;
return m_position = m_target;
}
std::size_t rounds = 1.0 + 1000.0 * duration; // 1 ms or shorter timesteps
double t = duration / rounds;
for (std::size_t i = 0; i < rounds; ++i) {
double d = remainder(m_target - m_position, num); // Distance (via shortest way)
// Allow it to stop nicely, without jitter
if (std::abs(m_velocity) < 0.1 && std::abs(d) < 0.001) {
m_velocity = 0.0;
m_position = m_target;
break;
}
double a = d > 0.0 ? acceleration : -acceleration; // Acceleration vector
// Are we going to right direction && can we stop in time if we start decelerating now?
if (d * m_velocity > 0.0 && std::abs(m_velocity) > 2.0 * overshoot * acceleration * d / m_velocity) a *= -1.0;
// Apply Newtonian mechanics
m_velocity += t * a;
m_position += t * m_velocity;
}
if ((m_position = fmod(m_position, num)) < 0.0) m_position += num; // Normalize to [0, num]
if (m_position > m_marginLeft + m_songs) m_position -= num; // Normalize to [-m_marginLeft, songs + m_marginRight]
return m_position;
}
/// get target
unsigned int getTarget() const { return m_target; };
/// set target
void setTarget(unsigned int target, unsigned int songs) {
m_target = target;
if (m_songs == songs) return;
// Number of songs has changed => reset animation
m_songs = songs;
m_position = target;
m_velocity = 0.0;
};
/// resets animation target to 0
void reset() { setTarget(0, 0); }
/// get current velocity
double getVelocity() const { return m_velocity; }
private:
unsigned int m_target;
unsigned int m_songs;
double m_position;
double m_velocity;
double m_marginLeft;
double m_marginRight;
Time m_time;
};
|