#pragma once #include "modern-qt/utility/animation/math.hh" #include "modern-qt/utility/animation/state/accessor.hh" #include namespace creeper { template struct PidState : public NormalAccessor { using ValueT = T; using Clock = std::chrono::steady_clock; using TimePoint = Clock::time_point; T value = animate::zero(); T target = animate::zero(); 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 last_error = animate::zero(); 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(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(); details.last_error = animate::zero(); 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; } }; }