KWin
Loading...
Searching...
No Matches
springmotion.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "springmotion.h"
8
9#include <cmath>
10
11namespace KWin
12{
13
14static qreal lerp(qreal a, qreal b, qreal t)
15{
16 return a * (1 - t) + b * t;
17}
18
20 : SpringMotion(200.0, 1.1)
21{
22}
23
24SpringMotion::SpringMotion(qreal springConstant, qreal dampingRatio)
25 : m_prev({0, 0})
26 , m_next({0, 0})
27 , m_t(1.0)
28 , m_timestep(1.0 / 100.0)
29 , m_anchor(0)
30 , m_springConstant(springConstant)
31 , m_dampingRatio(dampingRatio)
32 , m_dampingCoefficient(2 * std::sqrt(m_springConstant) * m_dampingRatio)
33 , m_epsilon(1.0)
34{
35}
36
38{
39 return std::fabs(position() - anchor()) > m_epsilon || std::fabs(velocity()) > m_epsilon;
40}
41
43{
44 return m_springConstant;
45}
46
48{
49 return m_dampingRatio;
50}
51
53{
54 return lerp(m_prev.velocity, m_next.velocity, m_t);
55}
56
57void SpringMotion::setVelocity(qreal velocity)
58{
59 m_next = State{
60 .position = position(),
61 .velocity = velocity,
62 };
63 m_t = 1.0;
64}
65
67{
68 return lerp(m_prev.position, m_next.position, m_t);
69}
70
71void SpringMotion::setPosition(qreal position)
72{
73 m_next = State{
74 .position = position,
75 .velocity = velocity(),
76 };
77 m_t = 1.0;
78}
79
81{
82 return m_epsilon;
83}
84
85void SpringMotion::setEpsilon(qreal epsilon)
86{
87 m_epsilon = epsilon;
88}
89
91{
92 return m_anchor;
93}
94
95void SpringMotion::setAnchor(qreal anchor)
96{
97 m_anchor = anchor;
98}
99
100SpringMotion::Slope SpringMotion::evaluate(const State &state, qreal dt, const Slope &slope)
101{
102 const State next{
103 .position = state.position + slope.dp * dt,
104 .velocity = state.velocity + slope.dv * dt,
105 };
106
107 // The math here follows from the mass-spring-damper model equation.
108 const qreal springForce = (m_anchor - next.position) * m_springConstant;
109 const qreal dampingForce = -next.velocity * m_dampingCoefficient;
110 const qreal acceleration = springForce + dampingForce;
111
112 return Slope{
113 .dp = state.velocity,
114 .dv = acceleration,
115 };
116}
117
118SpringMotion::State SpringMotion::integrate(const State &state, qreal dt)
119{
120 // Use Runge-Kutta method (RK4) to integrate the mass-spring-damper equation.
121 const Slope initial{
122 .dp = 0,
123 .dv = 0,
124 };
125 const Slope k1 = evaluate(state, 0.0, initial);
126 const Slope k2 = evaluate(state, 0.5 * dt, k1);
127 const Slope k3 = evaluate(state, 0.5 * dt, k2);
128 const Slope k4 = evaluate(state, dt, k3);
129
130 const qreal dpdt = 1.0 / 6.0 * (k1.dp + 2 * k2.dp + 2 * k3.dp + k4.dp);
131 const qreal dvdt = 1.0 / 6.0 * (k1.dv + 2 * k2.dv + 2 * k3.dv + k4.dv);
132
133 return State{
134 .position = state.position + dpdt * dt,
135 .velocity = state.velocity + dvdt * dt,
136 };
137}
138
139void SpringMotion::advance(std::chrono::milliseconds delta)
140{
141 if (!isMoving()) {
142 return;
143 }
144
145 // If m_springConstant is infinite, we have an animation time factor of zero.
146 // As such, we should advance to the target immediately.
147 if (std::isinf(m_springConstant)) {
148 m_next = State{
149 .position = m_anchor,
150 .velocity = 0.0,
151 };
152 return;
153 }
154
155 // If the delta interval is not multiple of m_timestep precisely, the previous and
156 // the next samples will be linearly interpolated to get current position and velocity.
157 const qreal steps = (delta.count() / 1000.0) / m_timestep;
158 for (m_t += steps; m_t > 1.0; m_t -= 1.0) {
159 m_prev = m_next;
160 m_next = integrate(m_next, m_timestep);
161 }
162}
163
164} // namespace KWin
qreal springConstant() const
void setEpsilon(qreal epsilon)
void advance(std::chrono::milliseconds delta)
qreal velocity() const
void setPosition(qreal position)
void setAnchor(qreal anchor)
qreal epsilon() const
void setVelocity(qreal velocity)
bool isMoving() const
qreal position() const
qreal anchor() const
qreal dampingRatio() const