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
//! Damped harmonic oscillator (DHO) suitable for animation.
//!
//! This implementation is derived from a JavaScript implementation at
//! [https://github.com/skevy/wobble](https://github.com/skevy/wobble). This is
//! the same implementation that is used by React Native. The only difference is
//! that we measure time in seconds, whereas in JavaScript time is measured in
//! milliseconds.

use num_traits::{Float, NumCast};

use super::{Approximation, Curve};

/// This is a spring whose oscillation and velocity we can efficiently
/// approximate.
#[derive(Copy, Clone, Debug)]
pub struct Spring<T>
where
    T: Float,
{
    /// Starting value of the animation.
    pub from_value: T,

    /// Ending value of the animation.
    pub to_value: T,

    /// The spring stiffness coefficient.
    pub stiffness: T,

    /// Defines how the spring's motion should be damped due to the forces of
    /// friction.
    pub damping: T,

    /// The mass of the object attached to the end of the spring.
    pub mass: T,

    /// The initial velocity (in units/ms) of the object attached to the spring.
    pub initial_velocity: T,

    /// Whether or not the spring allows "overdamping" (a damping ratio > 1).
    pub allows_overdamping: bool,

    /// False when overshooting is allowed, true when it is not.
    pub overshoot_clamping: bool,
}

impl<T> Default for Spring<T>
where
    T: Float,
{
    fn default() -> Self {
        Spring {
            from_value: T::zero(),
            to_value: T::zero(),
            stiffness: <T as NumCast>::from(100.0).unwrap(),
            damping: <T as NumCast>::from(10.0).unwrap(),
            mass: <T as NumCast>::from(1.0).unwrap(),
            initial_velocity: <T as NumCast>::from(1.0).unwrap(),
            allows_overdamping: false,
            overshoot_clamping: false,
        }
    }
}

impl<T> Curve for Spring<T>
where
    T: Float,
{
    type Value = T;
    type Velocity = T;

    /// This function approximates a spring's oscillation and velocity at the
    /// given timestamp.
    fn approximate(&self, time: f32) -> Approximation<T> {
        let time = <T as NumCast>::from(time * 1000.0).unwrap();

        let c = self.damping;
        let m = self.mass;
        let k = self.stiffness;
        let from_value = self.from_value;
        let to_value = self.to_value;
        let v0 = -self.initial_velocity;

        assert!(m > T::zero(), "Mass value must be greater than 0.");
        assert!(k > T::zero(), "Stiffness value must be greater than 0.");
        assert!(c > T::zero(), "Damping value must be greater than 0.");

        // Damping ratio (dimensionless).
        let mut zeta = c / (<T as NumCast>::from(2.0).unwrap() * (k * m).sqrt());

        // Undamped angular frequency of the oscillator (rad/ms).
        let omega0 = (k / m).sqrt() / <T as NumCast>::from(1000.0).unwrap();

        // Exponential decay.
        let omega1 = omega0 * (T::one() - zeta * zeta).sqrt();

        // Frequency of damped oscillation.
        let omega2 = omega0 * (zeta * zeta - T::one()).sqrt();

        // Initial displacement of the spring at t = 0.
        let x0 = to_value - from_value;

        // Disable overdamping if requested.
        if zeta > T::one() && !self.allows_overdamping {
            zeta = T::one();
        }

        if zeta < T::one() {
            // Under damped.
            let envelope = (-zeta * omega0 * time).exp();

            let oscillation = to_value
                - envelope
                    * ((v0 + zeta * omega0 * x0) / omega1 * (omega1 * time).sin()
                        + x0 * (omega1 * time).cos());

            // This looks crazy -- it's actually just the derivative of the
            // oscillation function.
            let velocity = zeta
                * omega0
                * envelope
                * ((omega1 * time).sin() * (v0 + zeta * omega0 * x0) / omega1
                    + x0 * (omega1 * time).cos())
                - envelope
                    * ((omega1 * time).cos() * (v0 + zeta * omega0 * x0)
                        - omega1 * x0 * (omega1 * time).sin());

            Approximation {
                value: oscillation,
                velocity,
            }
        } else if zeta == T::one() {
            // Critically damped.
            let envelope = (-omega0 * time).exp();
            let oscillation = to_value - envelope * (x0 + (v0 + omega0 * x0) * time);
            let velocity =
                envelope * (v0 * (time * omega0 - T::one()) + time * x0 * (omega0 * omega0));

            Approximation {
                value: oscillation,
                velocity,
            }
        } else {
            // Overdamped.
            let envelope = (-zeta * omega0 * time).exp();
            let oscillation = to_value
                - envelope
                    * ((v0 + zeta * omega0 * x0) * (omega2 * time).sinh()
                        + omega2 * x0 * (omega2 * time).cosh())
                    / omega2;
            let velocity = envelope
                * zeta
                * omega0
                * ((omega2 * time).sinh() * (v0 + zeta * omega0 * x0)
                    + x0 * omega2 * (omega2 * time).cosh())
                / omega2
                - envelope
                    * (omega2 * (omega2 * time).cosh() * (v0 + zeta * omega0 * x0)
                        + omega2 * omega2 * x0 * (omega2 * time).sinh())
                    / omega2;

            Approximation {
                value: oscillation,
                velocity,
            }
        }
    }

    fn target(&self) -> T {
        self.to_value
    }
}

#[cfg(test)]
mod tests {
    use super::super::Sampler;
    use super::*;

    #[test]
    fn test_spring() {
        let spring = Spring {
            from_value: 0.0,
            to_value: 320.0,
            stiffness: 100.0,
            damping: 10.0,
            mass: 1.0,
            initial_velocity: 0.0,
            overshoot_clamping: false,
            allows_overdamping: false,
        };

        let y = Sampler::new(&spring, 20.0)
            .map(|approx| approx.value)
            .collect::<Vec<_>>();

        println!("y: {:?}", y);
        println!("y: {:?}", y.len());
    }

    #[test]
    fn test_spring_ios() {
        let spring = Spring {
            from_value: 0.0,
            to_value: 320.0,
            stiffness: 1000.0,
            damping: 500.0,
            mass: 3.0,
            initial_velocity: 0.0,
            overshoot_clamping: false,
            allows_overdamping: true,
        };

        let y = Sampler::new(&spring, 20.0)
            .map(|approx| approx.value)
            .collect::<Vec<_>>();

        println!("y: {:?}", y);
        println!("y: {:?}", y.len());
    }
}