first commit
This commit is contained in:
12
modern-qt/utility/animation/state/accessor.hh
Normal file
12
modern-qt/utility/animation/state/accessor.hh
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace creeper {
|
||||
|
||||
struct NormalAccessor {
|
||||
auto get_value(this auto const& self) { return self.value; }
|
||||
auto set_value(this auto& self, auto const& t) { self.value = t; }
|
||||
auto get_target(this auto const& self) { return self.target; }
|
||||
auto set_target(this auto& self, auto const& t) { self.target = t; }
|
||||
};
|
||||
|
||||
}
|
||||
75
modern-qt/utility/animation/state/linear.hh
Normal file
75
modern-qt/utility/animation/state/linear.hh
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/animation/math.hh"
|
||||
#include "modern-qt/utility/animation/state/accessor.hh"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace creeper {
|
||||
|
||||
template <typename T>
|
||||
struct LinearState : public NormalAccessor {
|
||||
using ValueT = T;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
T value = animate::zero<T>();
|
||||
T target = animate::zero<T>();
|
||||
|
||||
struct {
|
||||
double speed = 1.0;
|
||||
double epsilon = 1e-2;
|
||||
} config;
|
||||
|
||||
struct {
|
||||
TimePoint last_timestamp;
|
||||
} details;
|
||||
|
||||
auto set_target(T new_target) noexcept -> void {
|
||||
target = new_target;
|
||||
|
||||
const auto current_time = Clock::now();
|
||||
using namespace std::chrono_literals;
|
||||
const auto threshold = 16ms;
|
||||
|
||||
const auto elapsed_time = current_time - details.last_timestamp;
|
||||
|
||||
if (elapsed_time > threshold) {
|
||||
details.last_timestamp = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
auto update() noexcept -> bool {
|
||||
const auto now = Clock::now();
|
||||
const auto duration = now - details.last_timestamp;
|
||||
const auto dt = std::chrono::duration<double>(duration).count();
|
||||
|
||||
if (dt <= 0.0) {
|
||||
details.last_timestamp = now;
|
||||
return animate::magnitude(target - value) > config.epsilon;
|
||||
}
|
||||
|
||||
const auto delta = target - value;
|
||||
const auto dist = animate::magnitude(delta);
|
||||
|
||||
if (dist <= config.epsilon) {
|
||||
value = target;
|
||||
details.last_timestamp = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto direction = animate::normalize(delta);
|
||||
const auto step = config.speed * dt;
|
||||
|
||||
if (step >= dist) {
|
||||
value = target;
|
||||
} else {
|
||||
value += direction * step;
|
||||
}
|
||||
|
||||
details.last_timestamp = now;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
94
modern-qt/utility/animation/state/pid.hh
Normal file
94
modern-qt/utility/animation/state/pid.hh
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/animation/math.hh"
|
||||
#include "modern-qt/utility/animation/state/accessor.hh"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace creeper {
|
||||
|
||||
template <typename T>
|
||||
struct PidState : public NormalAccessor {
|
||||
using ValueT = T;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
T value = animate::zero<T>();
|
||||
T target = animate::zero<T>();
|
||||
|
||||
struct {
|
||||
double kp = 1.0;
|
||||
double ki = 0.0;
|
||||
double kd = 0.1;
|
||||
double epsilon = 1e-3;
|
||||
} config;
|
||||
|
||||
struct {
|
||||
T integral_error = animate::zero<T>();
|
||||
T last_error = animate::zero<T>();
|
||||
TimePoint last_timestamp;
|
||||
} details;
|
||||
|
||||
auto set_target(T new_target) noexcept -> void {
|
||||
target = new_target;
|
||||
|
||||
const auto current_time = Clock::now();
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
const auto threshold = 16ms;
|
||||
|
||||
const auto elapsed_time = current_time - details.last_timestamp;
|
||||
|
||||
if (elapsed_time > threshold) {
|
||||
details.last_error = target - value;
|
||||
details.last_timestamp = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
auto update() noexcept -> bool {
|
||||
|
||||
const auto kp = config.kp;
|
||||
const auto ki = config.ki;
|
||||
const auto kd = config.kd;
|
||||
|
||||
const auto now = Clock::now();
|
||||
const auto duration = now - details.last_timestamp;
|
||||
|
||||
const auto dt = std::chrono::duration<double>(duration).count();
|
||||
|
||||
if (dt <= 0.0) {
|
||||
details.last_timestamp = now;
|
||||
return animate::magnitude(target - value) > config.epsilon;
|
||||
}
|
||||
|
||||
const auto current_error = target - value;
|
||||
|
||||
if (animate::magnitude(current_error) <= config.epsilon
|
||||
&& animate::magnitude(details.last_error) <= config.epsilon) {
|
||||
value = target;
|
||||
details.integral_error = animate::zero<T>();
|
||||
details.last_error = animate::zero<T>();
|
||||
details.last_timestamp = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto proportional_term = kp * current_error;
|
||||
|
||||
details.integral_error += current_error * dt;
|
||||
const auto integral_term = ki * details.integral_error;
|
||||
|
||||
const auto derivative_error = (current_error - details.last_error) / dt;
|
||||
const auto derivative_term = kd * derivative_error;
|
||||
|
||||
const auto output = proportional_term + integral_term + derivative_term;
|
||||
|
||||
value += output * dt;
|
||||
|
||||
details.last_error = current_error;
|
||||
details.last_timestamp = now;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
72
modern-qt/utility/animation/state/spring.hh
Normal file
72
modern-qt/utility/animation/state/spring.hh
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/animation/math.hh"
|
||||
#include "modern-qt/utility/animation/state/accessor.hh"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace creeper {
|
||||
|
||||
template <typename T>
|
||||
struct SpringState : public NormalAccessor {
|
||||
using ValueT = T;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
T value;
|
||||
T target;
|
||||
|
||||
T velocity = animate::zero<T>();
|
||||
|
||||
TimePoint last_timestamp = Clock::now();
|
||||
|
||||
struct {
|
||||
double k = 1.0;
|
||||
double d = 0.1;
|
||||
double epsilon = 1e-1;
|
||||
} config;
|
||||
|
||||
auto set_target(T new_target) noexcept -> void {
|
||||
target = new_target;
|
||||
|
||||
const auto current_time = Clock::now();
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
const auto threshold = 16ms;
|
||||
|
||||
const auto elapsed_time = current_time - last_timestamp;
|
||||
|
||||
if (elapsed_time > threshold) {
|
||||
const auto error = target - value;
|
||||
velocity = animate::zero<T>();
|
||||
last_timestamp = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
auto update() noexcept -> bool {
|
||||
const auto now = Clock::now();
|
||||
const auto duration = now - last_timestamp;
|
||||
const double dt = std::chrono::duration<double>(duration).count();
|
||||
|
||||
if (dt <= 0.0) {
|
||||
last_timestamp = now;
|
||||
return std::abs(animate::magnitude(target - value)) > config.epsilon;
|
||||
}
|
||||
|
||||
const auto error = value - target;
|
||||
const auto a_force = -config.k * error;
|
||||
const auto a_damping = -config.d * velocity;
|
||||
const auto a_total = a_force + a_damping;
|
||||
|
||||
velocity += a_total * dt;
|
||||
value += velocity * dt;
|
||||
|
||||
last_timestamp = now;
|
||||
|
||||
const bool done =
|
||||
animate::magnitude(error) < config.epsilon && std::abs(velocity) < config.epsilon;
|
||||
|
||||
if (done) velocity = animate::zero<T>();
|
||||
return !done;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user