1use crate::error::{JsResult, js_error, non_negative};
4use crate::tween::lock;
5use crate::types::f32_array;
6use animato_core::Update;
7use animato_spring::{Spring as CoreSpring, SpringConfig, SpringN};
8use js_sys::Float32Array;
9use std::sync::{Arc, Mutex};
10use wasm_bindgen::prelude::*;
11
12fn preset(name: &str) -> JsResult<SpringConfig> {
13 match crate::types::normalize_name(name).as_str() {
14 "gentle" => Ok(SpringConfig::gentle()),
15 "wobbly" => Ok(SpringConfig::wobbly()),
16 "stiff" => Ok(SpringConfig::stiff()),
17 "slow" => Ok(SpringConfig::slow()),
18 "snappy" => Ok(SpringConfig::snappy()),
19 _ => Err(js_error(format!("unknown spring preset `{name}`"))),
20 }
21}
22
23fn config(stiffness: f32, damping: f32, mass: f32, epsilon: f32) -> SpringConfig {
24 SpringConfig {
25 stiffness: non_negative(stiffness, 100.0),
26 damping: non_negative(damping, 10.0),
27 mass: non_negative(mass, 1.0).max(f32::EPSILON),
28 epsilon: non_negative(epsilon, 0.001),
29 }
30}
31
32#[derive(Clone, Debug)]
34pub(crate) struct SharedSpring {
35 inner: Arc<Mutex<CoreSpring>>,
36}
37
38impl SharedSpring {
39 pub(crate) fn new(inner: Arc<Mutex<CoreSpring>>) -> Self {
40 Self { inner }
41 }
42}
43
44impl Update for SharedSpring {
45 fn update(&mut self, dt: f32) -> bool {
46 lock(&self.inner).update(dt)
47 }
48}
49
50macro_rules! shared_spring_n {
51 ($name:ident, $value_ty:ty) => {
52 #[derive(Clone, Debug)]
54 pub(crate) struct $name {
55 inner: Arc<Mutex<SpringN<$value_ty>>>,
56 }
57
58 impl $name {
59 pub(crate) fn new(inner: Arc<Mutex<SpringN<$value_ty>>>) -> Self {
60 Self { inner }
61 }
62 }
63
64 impl Update for $name {
65 fn update(&mut self, dt: f32) -> bool {
66 lock(&self.inner).update(dt)
67 }
68 }
69 };
70}
71
72shared_spring_n!(SharedSpring2D, [f32; 2]);
73shared_spring_n!(SharedSpring3D, [f32; 3]);
74shared_spring_n!(SharedSpring4D, [f32; 4]);
75
76#[wasm_bindgen(js_name = Spring)]
78#[derive(Clone, Debug)]
79pub struct Spring {
80 inner: Arc<Mutex<CoreSpring>>,
81}
82
83#[wasm_bindgen(js_class = Spring)]
84impl Spring {
85 #[wasm_bindgen(constructor)]
87 pub fn new(initial: f32, target: f32) -> Self {
88 let mut spring = CoreSpring::new(SpringConfig::default());
89 spring.snap_to(initial);
90 spring.set_target(target);
91 Self {
92 inner: Arc::new(Mutex::new(spring)),
93 }
94 }
95
96 pub fn update(&self, dt: f32) -> bool {
98 lock(&self.inner).update(dt)
99 }
100
101 pub fn position(&self) -> f32 {
103 lock(&self.inner).position()
104 }
105
106 pub fn velocity(&self) -> f32 {
108 lock(&self.inner).velocity()
109 }
110
111 #[wasm_bindgen(js_name = isSettled)]
113 pub fn is_settled(&self) -> bool {
114 lock(&self.inner).is_settled()
115 }
116
117 #[wasm_bindgen(js_name = setTarget)]
119 pub fn set_target(&self, target: f32) {
120 lock(&self.inner).set_target(target);
121 }
122
123 #[wasm_bindgen(js_name = snapTo)]
125 pub fn snap_to(&self, position: f32) {
126 lock(&self.inner).snap_to(position);
127 }
128
129 #[wasm_bindgen(js_name = setPreset)]
131 pub fn set_preset(&self, name: &str) -> Result<(), JsValue> {
132 lock(&self.inner).config = preset(name)?;
133 Ok(())
134 }
135
136 #[wasm_bindgen(js_name = setConfig)]
138 pub fn set_config(&self, stiffness: f32, damping: f32, mass: f32, epsilon: f32) {
139 lock(&self.inner).config = config(stiffness, damping, mass, epsilon);
140 }
141
142 pub(crate) fn shared(&self) -> SharedSpring {
143 SharedSpring::new(Arc::clone(&self.inner))
144 }
145}
146
147macro_rules! vector_spring {
148 (
149 $class:ident,
150 $js_name:ident,
151 $shared:ident,
152 $value_ty:ty,
153 [$($initial:ident),+],
154 [$($target:ident),+],
155 $array_fn:ident
156 ) => {
157 #[wasm_bindgen(js_name = $js_name)]
159 #[derive(Clone, Debug)]
160 pub struct $class {
161 inner: Arc<Mutex<SpringN<$value_ty>>>,
162 }
163
164 #[wasm_bindgen(js_class = $js_name)]
165 impl $class {
166 #[wasm_bindgen(constructor)]
168 #[allow(clippy::too_many_arguments)]
169 pub fn new($($initial: f32,)+ $($target: f32),+) -> Self {
170 let mut spring = SpringN::new(SpringConfig::default(), [$($initial),+]);
171 spring.set_target([$($target),+]);
172 Self {
173 inner: Arc::new(Mutex::new(spring)),
174 }
175 }
176
177 pub fn update(&self, dt: f32) -> bool {
179 lock(&self.inner).update(dt)
180 }
181
182 #[wasm_bindgen(js_name = toArray)]
184 pub fn to_array(&self) -> Float32Array {
185 let value = lock(&self.inner).position();
186 f32_array(&value)
187 }
188
189 #[wasm_bindgen(js_name = isSettled)]
191 pub fn is_settled(&self) -> bool {
192 lock(&self.inner).is_settled()
193 }
194
195 #[wasm_bindgen(js_name = snapTo)]
197 pub fn snap_to(&self, $($initial: f32),+) {
198 lock(&self.inner).snap_to([$($initial),+]);
199 }
200
201 #[wasm_bindgen(js_name = setTarget)]
203 pub fn set_target(&self, $($target: f32),+) {
204 lock(&self.inner).set_target([$($target),+]);
205 }
206
207 pub(crate) fn shared(&self) -> $shared {
208 $shared::new(Arc::clone(&self.inner))
209 }
210 }
211 };
212}
213
214vector_spring!(
215 Spring2D,
216 Spring2D,
217 SharedSpring2D,
218 [f32; 2],
219 [x, y],
220 [target_x, target_y],
221 vec2
222);
223vector_spring!(
224 Spring3D,
225 Spring3D,
226 SharedSpring3D,
227 [f32; 3],
228 [x, y, z],
229 [target_x, target_y, target_z],
230 vec3
231);
232vector_spring!(
233 Spring4D,
234 Spring4D,
235 SharedSpring4D,
236 [f32; 4],
237 [x, y, z, w],
238 [target_x, target_y, target_z, target_w],
239 vec4
240);
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn scalar_spring_moves() {
248 let spring = Spring::new(0.0, 100.0);
249 spring.update(1.0 / 60.0);
250 assert!(spring.position() > 0.0);
251 }
252}