ferroui_base/rendering/composition/animations/
natural_motion_animation_instance.rs1use super::animation_controller::AnimationControllerClock;
5use super::{AnimationDelayBehavior, AnimationInstanceBase, AnimationStopBehavior, IAnimationInstance, PropertySetSnapshot};
6use crate::numerics::{Vector2, Vector3};
7use crate::rendering::composition::expressions::{Expression, ExpressionVariant, ExpressionVariantValue};
8use crate::rendering::composition::server::{CompositionProperty, IServerClockItem, ServerCompositor, ServerObjectId};
9use crate::rendering::composition::AnimationCompletion;
10use std::cell::{Cell, RefCell};
11use std::collections::HashSet;
12use std::rc::{Rc, Weak};
13use std::time::Duration;
14
15fn ticks(d: Duration) -> i64 {
17 (d.as_nanos() / 100) as i64
18}
19
20pub trait SpringValue: ExpressionVariantValue {
23 const COMPONENTS: usize;
25
26 fn to_components(self) -> [f64; 3];
28
29 fn from_components(components: [f64; 3]) -> Self;
31}
32
33impl SpringValue for f32 {
34 const COMPONENTS: usize = 1;
35
36 fn to_components(self) -> [f64; 3] {
37 [self as f64, 0.0, 0.0]
38 }
39
40 fn from_components(components: [f64; 3]) -> f32 {
41 components[0] as f32
42 }
43}
44
45impl SpringValue for Vector2 {
46 const COMPONENTS: usize = 2;
47
48 fn to_components(self) -> [f64; 3] {
49 [self.x as f64, self.y as f64, 0.0]
50 }
51
52 fn from_components(components: [f64; 3]) -> Vector2 {
53 Vector2::new(components[0] as f32, components[1] as f32)
54 }
55}
56
57impl SpringValue for Vector3 {
58 const COMPONENTS: usize = 3;
59
60 fn to_components(self) -> [f64; 3] {
61 [self.x as f64, self.y as f64, self.z as f64]
62 }
63
64 fn from_components(components: [f64; 3]) -> Vector3 {
65 Vector3::new(components[0] as f32, components[1] as f32, components[2] as f32)
66 }
67}
68
69const CRITICAL_DAMPING_TOLERANCE: f64 = 1e-6;
71
72pub fn spring_state(x0: f64, v0: f64, omega: f64, zeta: f64, t: f64) -> (f64, f64) {
82 if t <= 0.0 {
83 return (x0, v0);
84 }
85 if zeta < 1.0 - CRITICAL_DAMPING_TOLERANCE {
86 let damped = omega * (1.0 - zeta * zeta).sqrt();
87 let decay = (-zeta * omega * t).exp();
88 let b = (v0 + zeta * omega * x0) / damped;
89 let (sin, cos) = (damped * t).sin_cos();
90 let x = decay * (x0 * cos + b * sin);
91 let v = decay * ((b * damped - zeta * omega * x0) * cos - (x0 * damped + zeta * omega * b) * sin);
92 (x, v)
93 } else if zeta <= 1.0 + CRITICAL_DAMPING_TOLERANCE {
94 let decay = (-omega * t).exp();
95 let b = v0 + omega * x0;
96 let x = decay * (x0 + b * t);
97 let v = decay * (b - omega * (x0 + b * t));
98 (x, v)
99 } else {
100 let root = (zeta * zeta - 1.0).sqrt();
101 let r1 = -omega * (zeta - root);
102 let r2 = -omega * (zeta + root);
103 let c2 = (v0 - r1 * x0) / (r2 - r1);
104 let c1 = x0 - c2;
105 let e1 = (r1 * t).exp();
106 let e2 = (r2 * t).exp();
107 (c1 * e1 + c2 * e2, c1 * r1 * e1 + c2 * r2 * e2)
108 }
109}
110
111fn shaped(value: ExpressionVariant, like: ExpressionVariant) -> ExpressionVariant {
116 match (like, value) {
117 (ExpressionVariant::Vector3D(_), ExpressionVariant::Vector3(v)) => ExpressionVariant::Vector3D(v.into()),
118 (ExpressionVariant::Vector(_), ExpressionVariant::Vector2(v)) => ExpressionVariant::Vector(v.into()),
119 _ => value,
120 }
121}
122
123#[derive(Clone, Copy)]
127pub struct SpringNaturalMotionParameters<T> {
128 pub final_value: Option<T>,
131 pub initial_value: Option<T>,
134 pub initial_velocity: T,
136 pub damping_ratio: f32,
139 pub period: Duration,
141 pub delay_behavior: AnimationDelayBehavior,
142 pub delay_time: Duration,
143 pub stop_behavior: AnimationStopBehavior,
144}
145
146pub struct SpringNaturalMotionAnimationInstance<T: SpringValue> {
156 base: AnimationInstanceBase,
157 spring: SpringNaturalMotionParameters<T>,
158 delay_time: i64,
159 started_at: Cell<i64>,
160 start: Cell<T>,
161 end: Cell<T>,
162 starting_variant: Cell<ExpressionVariant>,
165 finished: Cell<bool>,
166 completion: RefCell<AnimationCompletion>,
167 initial_values: RefCell<Vec<(String, Expression)>>,
168 clock: AnimationControllerClock,
169}
170
171impl<T: SpringValue> SpringNaturalMotionAnimationInstance<T> {
172 pub fn new(
173 spring: SpringNaturalMotionParameters<T>,
174 target: ServerObjectId,
175 snapshot: Rc<PropertySetSnapshot>,
176 ) -> Rc<SpringNaturalMotionAnimationInstance<T>> {
177 Rc::new_cyclic(|this: &Weak<SpringNaturalMotionAnimationInstance<T>>| SpringNaturalMotionAnimationInstance {
178 base: AnimationInstanceBase::new(this.clone(), target, snapshot),
179 delay_time: ticks(spring.delay_time),
180 spring,
181 started_at: Cell::new(0),
182 start: Cell::new(T::default()),
183 end: Cell::new(T::default()),
184 starting_variant: Cell::new(ExpressionVariant::Invalid),
185 finished: Cell::new(false),
186 completion: RefCell::new(AnimationCompletion::none()),
187 initial_values: RefCell::new(Vec::new()),
188 clock: AnimationControllerClock::default(),
189 })
190 }
191
192 pub fn set_completion(&self, completion: AnimationCompletion) {
194 *self.completion.borrow_mut() = completion;
195 }
196
197 pub fn set_initial_value_expressions(&self, expressions: Vec<(String, Expression)>) {
200 *self.initial_values.borrow_mut() = expressions;
201 }
202
203 fn release_completion_now(&self) {
204 let mut completion = std::mem::take(&mut *self.completion.borrow_mut());
206 completion.release();
207 }
208
209 fn leave_clock(&self) {
210 if let (Some(compositor), Some(this)) = (self.base.compositor(), self.base.this_clock_item()) {
211 compositor.animations().remove_from_clock(&this);
212 }
213 }
214
215 fn variant(&self, value: T) -> ExpressionVariant {
216 shaped(ExpressionVariant::create(value), self.starting_variant.get())
217 }
218
219 fn state_at(&self, t: f64) -> (T, bool) {
222 let start = self.start.get().to_components();
223 let end = self.end.get().to_components();
224 let velocity = self.spring.initial_velocity.to_components();
225 let period = self.spring.period.as_secs_f64();
226 let omega = std::f64::consts::TAU / period;
227 let zeta = (self.spring.damping_ratio as f64).max(0.0);
228 let mut scale: f64 = 1.0;
232 for i in 0..T::COMPONENTS {
233 scale = scale.max((start[i] - end[i]).abs()).max(velocity[i].abs() / omega);
234 }
235 let position_epsilon = 1e-4 * scale;
236 let velocity_epsilon = position_epsilon * omega;
237 let mut value = [0.0; 3];
238 let mut at_rest = true;
239 for i in 0..T::COMPONENTS {
240 let (x, v) = spring_state(start[i] - end[i], velocity[i], omega, zeta, t);
241 value[i] = end[i] + x;
242 if x.abs() > position_epsilon || v.abs() > velocity_epsilon {
243 at_rest = false;
244 }
245 }
246 (T::from_components(value), at_rest)
247 }
248
249 fn evaluate_core(&self, now: Duration, current_value: ExpressionVariant) -> ExpressionVariant {
250 let current = if current_value == ExpressionVariant::Invalid { self.starting_variant.get() } else { current_value };
251 if self.finished.get() {
252 return self.variant(self.end.get());
253 }
254 let elapsed = self.clock.elapsed(self.base.compositor().as_ref(), ticks(now), self.started_at.get(), None);
255 if elapsed < self.delay_time {
256 return match self.spring.delay_behavior {
257 AnimationDelayBehavior::SetInitialValueBeforeDelay => self.variant(self.start.get()),
258 AnimationDelayBehavior::SetInitialValueAfterDelay => current,
259 };
260 }
261 let t = (elapsed - self.delay_time) as f64 / 10_000_000.0;
262 let (value, at_rest) = self.state_at(t);
263 if at_rest && !self.clock.is_paused() {
265 self.leave_clock();
266 self.finished.set(true);
267 self.release_completion_now();
269 return self.variant(self.end.get());
270 }
271 self.variant(value)
272 }
273}
274
275impl<T: SpringValue> IServerClockItem for SpringNaturalMotionAnimationInstance<T> {
276 fn on_tick(&self) {
277 if self.base.target().is_none() {
279 self.leave_clock();
280 self.release_completion_now();
281 return;
282 }
283 self.base.on_tick()
284 }
285}
286
287impl<T: SpringValue> IAnimationInstance for SpringNaturalMotionAnimationInstance<T> {
288 fn target_object(&self) -> ServerObjectId {
289 self.base.target_object()
290 }
291
292 fn resolve(&self, compositor: &Rc<ServerCompositor>) {
293 self.base.resolve(compositor)
294 }
295
296 fn evaluate(&self, now: Duration, current_value: ExpressionVariant) -> ExpressionVariant {
297 self.base.begin_evaluate();
298 self.evaluate_core(now, current_value)
299 }
300
301 fn initialize(&self, started_at: Duration, starting_value: ExpressionVariant, property: &'static CompositionProperty) {
302 let initial_values = std::mem::take(&mut *self.initial_values.borrow_mut());
303 let starting_value = self.base.apply_initial_values(&initial_values, property, starting_value);
304 self.starting_variant.set(starting_value);
305 let current = starting_value.try_cast::<T>().unwrap_or_default();
306 self.start.set(self.spring.initial_value.unwrap_or(current));
307 self.end.set(self.spring.final_value.unwrap_or(current));
308 self.started_at.set(ticks(started_at));
309 self.base.initialize(property, &HashSet::new());
310 }
311
312 fn activate(&self) {
313 if self.finished.get() {
314 return;
315 }
316 if let (Some(compositor), Some(this)) = (self.base.compositor(), self.base.this_clock_item()) {
317 compositor.animations().add_to_clock(this);
318 }
319 self.base.activate();
320 }
321
322 fn deactivate(&self) {
323 self.leave_clock();
324 self.base.deactivate();
325 }
326
327 fn invalidate(&self) {
328 self.base.invalidate()
329 }
330
331 fn value_on_stop(&self, _current_value: ExpressionVariant) -> Option<ExpressionVariant> {
332 match self.spring.stop_behavior {
333 AnimationStopBehavior::LeaveCurrentValue => None,
334 AnimationStopBehavior::SetToInitialValue => Some(self.variant(self.start.get())),
335 AnimationStopBehavior::SetToFinalValue => Some(self.variant(self.end.get())),
336 }
337 }
338
339 fn release_completion(&self) {
340 self.release_completion_now()
341 }
342
343 fn attach_controller(&self, controller: ServerObjectId) {
344 self.clock.attach(controller)
345 }
346}