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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
//! 一个弹簧算法类

use std::{f64::consts::E, time::Instant};

type Num = f64;

#[inline]
/// Fast rounding for x <= 2^23.
/// This is orders of magnitude faster than built-in rounding intrinsic.
///
/// Source: <https://stackoverflow.com/a/42386149/585725>
pub fn round(mut x: Num) -> Num {
    x += 12582912.0;
    x -= 12582912.0;
    x
}

/// 一个一维弹簧,可用于动画用途
///
/// 优点是调用次数无关,可以在任意时间计算出当前时间的弹簧末端位置,非常方便
pub struct Spring {
    start_time: Instant,
    damper: Num,
    velocity: Num,
    speed: Num,
    target: Num,
    position: Num,
}

impl Spring {
    /// 根据初始位置创建一个新的弹簧
    pub fn new(start_position: Num) -> Self {
        Self {
            start_time: Instant::now(),
            position: start_position,
            damper: 0.95,
            velocity: 0.,
            speed: 1.,
            target: start_position,
        }
    }

    fn position_velocity(&mut self) -> (Num, Num) {
        let x = self.start_time.elapsed().as_secs_f64();
        let c0 = self.position - self.target;
        if self.speed == 0. {
            (self.position, 0.)
        } else if self.damper < 1. {
            let c = (1. - self.damper.powi(2)).sqrt();
            let c1 = (self.velocity / self.speed + self.damper * c0) / c;
            let co = (c * self.speed * x).cos();
            let si = (c * self.speed * x).sin();
            let e = E.powf(self.damper * self.speed * x);
            (
                self.target + (c0 * co + c1 * si) / e,
                self.speed * ((c * c1 - self.damper * c0) * co - (c * c0 + self.damper * c1) * si)
                    / e,
            )
        } else {
            let c1 = self.velocity / self.speed + c0;
            let e = E.powf(self.speed * x);
            (
                self.target + (c0 + c1 * self.speed * x) / e,
                self.speed * (c1 - c0 - c1 * self.speed * x) / e,
            )
        }
    }

    /// 判断弹簧是否已到达目标位置
    ///
    /// 既速度为零的情况下已到达目标位置
    pub fn arrived(&mut self) -> bool {
        let (pos, vel) = self.position_velocity();
        (round(pos * 10.) - round(self.target * 10.)).abs() < f64::EPSILON && round(vel * 10.) == 0.
    }

    /// 获得当前弹簧所在位置
    ///
    /// 会因为调用的时间不同而变化
    pub fn position(&mut self) -> Num {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        r.0
    }

    /// 获得当前弹簧所在位置,但是会四舍五入成整数
    pub fn position_rounded(&mut self) -> Num {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        round(r.0)
    }

    /// 获取当前弹簧的移动速度
    ///
    /// 会因为调用的时间不同而变化
    pub fn velocity(&mut self) -> Num {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        r.1
    }

    /// 获取当前弹簧的加速度
    ///
    /// 会因为调用的时间不同而变化
    pub fn acceleration(&self) -> Num {
        let x = self.start_time.elapsed().as_secs_f64();
        let c0 = self.position - x;
        if self.speed == 0. {
            0.
        } else if self.damper < 1. {
            let c = (1. - self.damper.powi(2)).sqrt();
            let c1 = (self.velocity / self.speed + self.damper * c0) / c;
            self.speed.powi(2)
                * ((self.damper.powi(2) * c0 - 2. * c * self.damper * c1 - c.powi(2) * c0)
                    * (c * self.speed * x).cos()
                    + (self.damper * self.damper * c1 + 2. * c * self.damper * c0 - c.powi(2) * c1)
                        * (c * self.speed * x).cos())
                / E.powf(self.damper * self.speed * x)
        } else {
            let c1 = self.velocity / self.speed + c0;
            self.speed.powi(2) * (c0 - 2. * c1 + c1 * self.speed * x) / E.powf(self.speed * x)
        }
    }

    fn reset_time(&mut self) {
        self.start_time = Instant::now();
    }

    /// 设置当前位置,注意这不是目标位置,如果当前位置不是目标位置则会发生移动
    pub fn set_position(&mut self, value: Num) {
        let r = self.position_velocity();
        self.position = value;
        self.velocity = r.1;
        self.reset_time();
    }

    /// 设置当前速度,此时弹簧会立刻改变速度
    pub fn set_velocity(&mut self, value: Num) {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = value;
        self.reset_time();
    }

    /// Builder 模式的 [`Spring::set_velocity`]
    pub fn with_velocity(mut self, value: Num) -> Self {
        self.set_velocity(value);
        self
    }

    /// 设置弹簧的阻尼
    ///
    /// 如果取值在 `[0.0 - 1.0)` 之间则会有回弹效果
    ///
    /// 如果大于等于 `1.0` 则会缓慢移动到目标位置而没有回弹效果
    pub fn set_damper(&mut self, value: Num) {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        self.damper = value;
        self.reset_time();
    }

    /// Builder 模式的 [`Spring::set_damper`]
    pub fn with_damper(mut self, value: Num) -> Self {
        self.set_damper(value);
        self
    }

    /// 设置弹簧的运行速度,数值越大弹簧的速度就越快
    pub fn set_speed(&mut self, value: Num) {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        self.speed = value;
        self.reset_time();
    }

    /// 返回弹簧的目标位置
    pub fn target(&self) -> Num {
        self.target
    }

    /// 设置弹簧的目标位置,此时弹簧会立刻开始变换
    pub fn set_target(&mut self, value: Num) {
        let r = self.position_velocity();
        self.position = r.0;
        self.velocity = r.1;
        self.target = value;
        self.reset_time();
    }
}

/// 一个二维弹簧,由两个一维弹簧组成
pub struct Spring2D {
    sx: Spring,
    sy: Spring,
}

impl Spring2D {
    /// 根据初始位置创建一个新的弹簧
    pub fn new(start_pos: (Num, Num)) -> Self {
        Self {
            sx: Spring::new(start_pos.0),
            sy: Spring::new(start_pos.1),
        }
    }

    /// 获得当前弹簧所在位置
    ///
    /// 会因为调用的时间不同而变化
    pub fn position(&mut self) -> (Num, Num) {
        (self.sx.position(), self.sy.position())
    }

    /// 获得当前弹簧所在位置,但是会四舍五入成整数
    pub fn position_rounded(&mut self) -> (Num, Num) {
        (self.sx.position_rounded(), self.sy.position_rounded())
    }

    /// 获取当前弹簧的移动速度
    ///
    /// 会因为调用的时间不同而变化
    pub fn velocity(&mut self) -> (Num, Num) {
        (self.sx.velocity(), self.sy.velocity())
    }

    /// 获取当前弹簧的阻尼
    pub fn damper(&self) -> Num {
        self.sx.damper
    }

    /// 获取弹簧的运行速度
    pub fn speed(&self) -> Num {
        (self.sx.speed.powi(2) + self.sy.speed.powi(2)).sqrt()
    }

    /// 获取弹簧的目标位置
    pub fn target(&self) -> (Num, Num) {
        (self.sx.target, self.sy.target)
    }

    /// 设置当前位置,注意这不是目标位置,如果当前位置不是目标位置则会发生移动
    pub fn set_position(&mut self, value: (Num, Num)) {
        self.sx.position = value.0;
        self.sy.position = value.1;
    }

    /// 设置当前速度,此时弹簧会立刻改变速度
    pub fn set_velocity(&mut self, value: (Num, Num)) {
        self.sx.velocity = value.0;
        self.sy.velocity = value.1;
    }

    /// 设置弹簧的阻尼
    ///
    /// 如果取值在 `[0.0 - 1.0)` 之间则会有回弹效果
    ///
    /// 如果大于等于 `1.0` 则会缓慢移动到目标位置而没有回弹效果
    pub fn set_damper(&mut self, value: Num) {
        self.sx.damper = value;
        self.sy.damper = value;
    }

    /// 设置弹簧的运行速度,数值越大弹簧的速度就越快
    pub fn set_speed(&mut self, value: Num) {
        self.sx.set_speed(value);
        self.sy.set_speed(value);
    }

    /// 设置弹簧的目标位置,此时弹簧会立刻开始变换
    pub fn set_target(&mut self, value: (Num, Num)) {
        self.sx.set_target(value.0);
        self.sy.set_target(value.1);
    }

    /// 判断弹簧是否已到达目标位置
    ///
    /// 既速度为零的情况下已到达目标位置
    pub fn arrived(&mut self) -> bool {
        self.sx.arrived() && self.sy.arrived()
    }
}

impl From<f64> for Spring {
    fn from(p: f64) -> Self {
        Self::new(p)
    }
}