skspringrs/
lib.rs

1// Damped spring motion
2
3//  Copyright (c) 2008-2012 Ryan Juckett http://www.ryanjuckett.com/
4//
5//  This software is provided 'as-is', without any express or implied
6//  warranty. In no event will the authors be held liable for any
7//  damages arising from the use of this software.
8//
9//  Permission is granted to anyone to use this software for any purpose,
10//  including commercial applications, and to alter it and redistribute
11//  it freely, subject to the following restrictions:
12//
13//  1. The origin of this software must not be misrepresented; you
14//  must not
15//     claim that you wrote the original software. If you use this
16//     software in a product, an acknowledgment in the product
17//     documentation would be appreciated but is not required.
18//
19//  2. Altered source versions must be plainly marked as such, and must
20//  not be
21//     misrepresented as being the original software.
22//
23//  3. This notice may not be removed or altered from any source
24//     distribution.
25
26// Sk: Consider the following code an 'altered source version' in
27// its entirety
28
29extern crate num;
30use num::Float;
31
32/// Caches coefficients so that multiple springs with the same
33/// parameters may be used without re-running trigonometry calls.
34pub struct DampedSpringController<F> {
35    pos_pos_coef: F,
36    pos_vel_coef: F,
37    vel_pos_coef: F,
38    vel_vel_coef: F,
39}
40
41impl<F> DampedSpringController<F>
42where
43    F: num::Float + num::cast::FromPrimitive
44{
45    /// Performs (somewhat nontrivial) calculations to prepare the
46    /// motion of a damped spring. Once initialized, you may update
47    /// any spring with identical properties with `update` using the
48    /// same object.
49    pub fn with_coefficients(delta_time: F, desired_angular_frequency: F, desired_damping_ratio: F) -> DampedSpringController<F> {
50        // clamping
51        let damping_ratio     = if desired_damping_ratio     < F::from_f64(0.0).unwrap() { F::from_f64(0.0).unwrap() } else { desired_damping_ratio };
52        let angular_frequency = if desired_angular_frequency < F::from_f64(0.0).unwrap() { F::from_f64(0.0).unwrap() } else { desired_angular_frequency };
53
54        // special case: no angular frequency
55        if angular_frequency < F::epsilon() {
56            DampedSpringController{
57                pos_pos_coef: F::from_f64(1.0).unwrap(),
58                pos_vel_coef: F::from_f64(0.0).unwrap(),
59                vel_pos_coef: F::from_f64(0.0).unwrap(),
60                vel_vel_coef: F::from_f64(1.0).unwrap()}
61        } else if damping_ratio > F::from_f64(1.0).unwrap() + F::epsilon() { // over-damped
62            let za = -angular_frequency * damping_ratio;
63            let zb = angular_frequency * Float::sqrt(damping_ratio * damping_ratio - F::from_f64(1.0).unwrap());
64            let z1 = za - zb;
65            let z2 = za + zb;
66            let e1 = Float::exp(z1 * delta_time);
67            let e2 = Float::exp(z2 * delta_time);
68            let inv_two_zb = F::from_f64(1.0).unwrap() / (F::from_f64(2.0).unwrap() * zb);
69            let e1_over_twozb = e1 * inv_two_zb;
70            let e2_over_twozb = e2 * inv_two_zb;
71            let z1e1_over_twozb = z1 * e1_over_twozb;
72            let z2e2_over_twozb = z2 * e2_over_twozb;
73            DampedSpringController {
74                pos_pos_coef: e1_over_twozb * z2 - z2e2_over_twozb + e2,
75                pos_vel_coef: -e1_over_twozb + e2_over_twozb,
76                vel_pos_coef: (z1e1_over_twozb - z2e2_over_twozb + e2) * z2,
77                vel_vel_coef: -z1e1_over_twozb + z2e2_over_twozb
78            }
79        } else if damping_ratio < F::from_f64(1.0).unwrap() - F::epsilon() { // under-damped
80            let omega_zeta = angular_frequency * damping_ratio;
81            let alpha = angular_frequency * Float::sqrt(F::from_f64(1.0).unwrap() - damping_ratio * damping_ratio);
82            let exp_term = Float::exp(-omega_zeta * delta_time);
83            let cos_term = Float::cos(alpha * delta_time);
84            let sin_term = Float::sin(alpha * delta_time);
85            let inv_alpha = F::from_f64(1.0).unwrap() / alpha;
86            let exp_sin = exp_term * sin_term;
87            let exp_cos = exp_term * cos_term;
88            let exp_omega_zeta_sin_over_alpha = exp_term * omega_zeta * sin_term * inv_alpha;
89            DampedSpringController {
90                pos_pos_coef: exp_cos + exp_omega_zeta_sin_over_alpha,
91                pos_vel_coef: exp_sin * inv_alpha,
92                vel_pos_coef: -exp_sin * alpha - omega_zeta * exp_omega_zeta_sin_over_alpha,
93                vel_vel_coef: exp_cos - exp_omega_zeta_sin_over_alpha
94            }
95        } else { // critically damped
96            let exp_term = Float::exp(-angular_frequency * delta_time);
97            let time_exp = delta_time * exp_term;
98            let time_exp_freq = time_exp * angular_frequency;
99            DampedSpringController {
100                pos_pos_coef: time_exp_freq + exp_term,
101                pos_vel_coef: time_exp,
102                vel_pos_coef: -angular_frequency * time_exp_freq,
103                vel_vel_coef: -time_exp_freq + exp_term
104            }
105        }
106    }
107
108    /// Updates the position and velocity of a spring using this
109    /// controller's physical properties.
110    pub fn update(&self, position: &mut F, velocity: &mut F, target: F) {
111        let old_pos = *position - target;
112        let old_vel = *velocity;
113        *position = old_pos * self.pos_pos_coef + old_vel * self.pos_vel_coef + target;
114        *velocity = old_pos * self.vel_pos_coef + old_vel * self.vel_vel_coef;
115    }
116}
117