File indexing completed on 2024-11-10 04:57:09
0001 /* 0002 SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "springmotion.h" 0008 0009 #include <cmath> 0010 0011 namespace KWin 0012 { 0013 0014 static qreal lerp(qreal a, qreal b, qreal t) 0015 { 0016 return a * (1 - t) + b * t; 0017 } 0018 0019 SpringMotion::SpringMotion() 0020 : SpringMotion(200.0, 1.1) 0021 { 0022 } 0023 0024 SpringMotion::SpringMotion(qreal springConstant, qreal dampingRatio) 0025 : m_prev({0, 0}) 0026 , m_next({0, 0}) 0027 , m_t(1.0) 0028 , m_timestep(1.0 / 100.0) 0029 , m_anchor(0) 0030 , m_springConstant(springConstant) 0031 , m_dampingRatio(dampingRatio) 0032 , m_dampingCoefficient(2 * std::sqrt(m_springConstant) * m_dampingRatio) 0033 , m_epsilon(1.0) 0034 { 0035 } 0036 0037 bool SpringMotion::isMoving() const 0038 { 0039 return std::fabs(position() - anchor()) > m_epsilon || std::fabs(velocity()) > m_epsilon; 0040 } 0041 0042 qreal SpringMotion::springConstant() const 0043 { 0044 return m_springConstant; 0045 } 0046 0047 qreal SpringMotion::dampingRatio() const 0048 { 0049 return m_dampingRatio; 0050 } 0051 0052 qreal SpringMotion::velocity() const 0053 { 0054 return lerp(m_prev.velocity, m_next.velocity, m_t); 0055 } 0056 0057 void SpringMotion::setVelocity(qreal velocity) 0058 { 0059 m_next = State{ 0060 .position = position(), 0061 .velocity = velocity, 0062 }; 0063 m_t = 1.0; 0064 } 0065 0066 qreal SpringMotion::position() const 0067 { 0068 return lerp(m_prev.position, m_next.position, m_t); 0069 } 0070 0071 void SpringMotion::setPosition(qreal position) 0072 { 0073 m_next = State{ 0074 .position = position, 0075 .velocity = velocity(), 0076 }; 0077 m_t = 1.0; 0078 } 0079 0080 qreal SpringMotion::epsilon() const 0081 { 0082 return m_epsilon; 0083 } 0084 0085 void SpringMotion::setEpsilon(qreal epsilon) 0086 { 0087 m_epsilon = epsilon; 0088 } 0089 0090 qreal SpringMotion::anchor() const 0091 { 0092 return m_anchor; 0093 } 0094 0095 void SpringMotion::setAnchor(qreal anchor) 0096 { 0097 m_anchor = anchor; 0098 } 0099 0100 SpringMotion::Slope SpringMotion::evaluate(const State &state, qreal dt, const Slope &slope) 0101 { 0102 const State next{ 0103 .position = state.position + slope.dp * dt, 0104 .velocity = state.velocity + slope.dv * dt, 0105 }; 0106 0107 // The math here follows from the mass-spring-damper model equation. 0108 const qreal springForce = (m_anchor - next.position) * m_springConstant; 0109 const qreal dampingForce = -next.velocity * m_dampingCoefficient; 0110 const qreal acceleration = springForce + dampingForce; 0111 0112 return Slope{ 0113 .dp = state.velocity, 0114 .dv = acceleration, 0115 }; 0116 } 0117 0118 SpringMotion::State SpringMotion::integrate(const State &state, qreal dt) 0119 { 0120 // Use Runge-Kutta method (RK4) to integrate the mass-spring-damper equation. 0121 const Slope initial{ 0122 .dp = 0, 0123 .dv = 0, 0124 }; 0125 const Slope k1 = evaluate(state, 0.0, initial); 0126 const Slope k2 = evaluate(state, 0.5 * dt, k1); 0127 const Slope k3 = evaluate(state, 0.5 * dt, k2); 0128 const Slope k4 = evaluate(state, dt, k3); 0129 0130 const qreal dpdt = 1.0 / 6.0 * (k1.dp + 2 * k2.dp + 2 * k3.dp + k4.dp); 0131 const qreal dvdt = 1.0 / 6.0 * (k1.dv + 2 * k2.dv + 2 * k3.dv + k4.dv); 0132 0133 return State{ 0134 .position = state.position + dpdt * dt, 0135 .velocity = state.velocity + dvdt * dt, 0136 }; 0137 } 0138 0139 void SpringMotion::advance(std::chrono::milliseconds delta) 0140 { 0141 if (!isMoving()) { 0142 return; 0143 } 0144 0145 // If m_springConstant is infinite, we have an animation time factor of zero. 0146 // As such, we should advance to the target immediately. 0147 if (std::isinf(m_springConstant)) { 0148 m_next = State{ 0149 .position = m_anchor, 0150 .velocity = 0.0, 0151 }; 0152 return; 0153 } 0154 0155 // If the delta interval is not multiple of m_timestep precisely, the previous and 0156 // the next samples will be linearly interpolated to get current position and velocity. 0157 const qreal steps = (delta.count() / 1000.0) / m_timestep; 0158 for (m_t += steps; m_t > 1.0; m_t -= 1.0) { 0159 m_prev = m_next; 0160 m_next = integrate(m_next, m_timestep); 0161 } 0162 } 0163 0164 } // namespace KWin