Skip to main content

ferroui_base/animation/
spring.rs

1use crate::utilities::{FormatError, SpanStringTokenizer, spring_solver::SpringSolver};
2use std::cell::Cell;
3use std::str::FromStr;
4
5/// Determines how an animation is used based on spring formula.
6pub struct Spring {
7    spring_solver: Cell<SpringSolver>,
8    mass: Cell<f64>,
9    stiffness: Cell<f64>,
10    damping: Cell<f64>,
11    initial_velocity: Cell<f64>,
12    is_dirty: Cell<bool>,
13}
14
15
16/// Compares by identity (reference equality), as the reference type this
17/// mirrors.
18impl PartialEq for Spring {
19    #[inline]
20    fn eq(&self, other: &Self) -> bool {
21        std::ptr::eq(self, other)
22    }
23}
24
25impl Default for Spring {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl Spring {
32    /// Creates a spring with all parameters zero.
33    pub fn new() -> Self {
34        Self::with_values(0.0, 0.0, 0.0, 0.0)
35    }
36
37    /// Creates a spring with the given parameters.
38    pub fn with_values(mass: f64, stiffness: f64, damping: f64, initial_velocity: f64) -> Self {
39        Self {
40            spring_solver: Cell::new(SpringSolver::default()),
41            mass: Cell::new(mass),
42            stiffness: Cell::new(stiffness),
43            damping: Cell::new(damping),
44            initial_velocity: Cell::new(initial_velocity),
45            is_dirty: Cell::new(true),
46        }
47    }
48
49    /// Parses a spring from four numbers: mass, stiffness, damping and
50    /// initial velocity.
51    pub fn parse(value: &str) -> Result<Spring, FormatError> {
52        let error = |_| FormatError::from_string(format!("Invalid Spring string: \"{value}\"."));
53        let mut tokenizer = SpanStringTokenizer::new(value);
54        let result = Spring::with_values(
55            tokenizer.read_double().map_err(error)?,
56            tokenizer.read_double().map_err(error)?,
57            tokenizer.read_double().map_err(error)?,
58            tokenizer.read_double().map_err(error)?,
59        );
60        tokenizer.finish().map_err(error)?;
61        Ok(result)
62    }
63
64    /// The spring mass.
65    pub fn mass(&self) -> f64 {
66        self.mass.get()
67    }
68
69    pub fn set_mass(&self, value: f64) {
70        self.mass.set(value);
71        self.is_dirty.set(true);
72    }
73
74    /// The spring stiffness.
75    pub fn stiffness(&self) -> f64 {
76        self.stiffness.get()
77    }
78
79    pub fn set_stiffness(&self, value: f64) {
80        self.stiffness.set(value);
81        self.is_dirty.set(true);
82    }
83
84    /// The spring damping.
85    pub fn damping(&self) -> f64 {
86        self.damping.get()
87    }
88
89    pub fn set_damping(&self, value: f64) {
90        self.damping.set(value);
91        self.is_dirty.set(true);
92    }
93
94    /// The spring initial velocity.
95    pub fn initial_velocity(&self) -> f64 {
96        self.initial_velocity.get()
97    }
98
99    pub fn set_initial_velocity(&self, value: f64) {
100        self.initial_velocity.set(value);
101        self.is_dirty.set(true);
102    }
103
104    /// Calculates the spring progress from a linear progress.
105    pub fn get_spring_progress(&self, linear_progress: f64) -> f64 {
106        if self.is_dirty.get() {
107            self.build();
108        }
109
110        self.spring_solver.get().solve(linear_progress)
111    }
112
113    /// Creates the spring solver.
114    fn build(&self) {
115        self.spring_solver.set(SpringSolver::new(
116            self.mass.get(),
117            self.stiffness.get(),
118            self.damping.get(),
119            self.initial_velocity.get(),
120        ));
121        self.is_dirty.set(false);
122    }
123}
124
125impl FromStr for Spring {
126    type Err = FormatError;
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        Spring::parse(s)
129    }
130}