Skip to main content

ferroui_base/rendering/composition/animations/
natural_motion_animation_instance.rs

1//! The server side of the spring natural motion animations: a damped harmonic
2//! oscillator per component.
3
4use 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
15/// The ticks (100 ns units) of a time span.
16fn ticks(d: Duration) -> i64 {
17    (d.as_nanos() / 100) as i64
18}
19
20/// A value a spring animation moves: its components move independently,
21/// each on the same spring.
22pub trait SpringValue: ExpressionVariantValue {
23    /// The number of components (1 to 3).
24    const COMPONENTS: usize;
25
26    /// The components, unused ones zero.
27    fn to_components(self) -> [f64; 3];
28
29    /// The value with the given components.
30    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
69/// How close to 1 a damping ratio is taken as critical damping.
70const CRITICAL_DAMPING_TOLERANCE: f64 = 1e-6;
71
72/// The state of a damped harmonic oscillator at time `t` (seconds): the
73/// displacement from the rest point and the velocity, for an initial
74/// displacement `x0`, an initial velocity `v0`, the undamped angular
75/// frequency `omega` (radians per second) and the damping ratio `zeta`.
76///
77/// The closed forms of the equation `x'' + 2 zeta omega x' + omega^2 x = 0`:
78/// under-damped (`zeta < 1`) the spring oscillates in a decaying envelope;
79/// critically damped (`zeta = 1`) and over-damped (`zeta > 1`) it returns
80/// without crossing the rest point when it starts at rest.
81pub 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
111/// `value` in the representation of `like` where both name the same vector:
112/// a `Vector3` spring on a property of `Vector3D` values (`Offset` of a
113/// visual) evaluates to a `Vector3D`, so expressions that read the property
114/// while it is animated see its type.
115fn 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/// What a spring natural motion animation is made of when it starts (the
124/// members of `SpringScalarNaturalMotionAnimation` and its vector siblings),
125/// in the form that crosses to the server.
126#[derive(Clone, Copy)]
127pub struct SpringNaturalMotionParameters<T> {
128    /// The value the spring comes to rest at: `FinalValue`, or the new value
129    /// of an implicit start; the current value of the property when `None`.
130    pub final_value: Option<T>,
131    /// The value the spring starts from; the current value of the property
132    /// when `None`.
133    pub initial_value: Option<T>,
134    /// The velocity the spring starts with, per second.
135    pub initial_velocity: T,
136    /// The damping ratio: 1 is critically damped, less oscillates, more
137    /// returns slower; 0 never comes to rest.
138    pub damping_ratio: f32,
139    /// The period of the undamped oscillation.
140    pub period: Duration,
141    pub delay_behavior: AnimationDelayBehavior,
142    pub delay_time: Duration,
143    pub stop_behavior: AnimationStopBehavior,
144}
145
146/// Server-side counterpart of the spring natural motion animations
147/// (`SpringScalarNaturalMotionAnimation`, `SpringVector2NaturalMotionAnimation`,
148/// `SpringVector3NaturalMotionAnimation`).
149///
150/// Each component of the value moves on a damped harmonic oscillator from the
151/// initial value with the initial velocity to the final value. The animation
152/// ends when every component is close enough to the final value and nearly at
153/// rest: it then sets the final value, leaves the clock and releases the
154/// batches it belongs to, as a key frame animation that finishes.
155pub 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    /// The value of the property when the animation started, which gives the
163    /// representation of the values the animation evaluates to.
164    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    /// Sets the batches the animation belongs to.
193    pub fn set_completion(&self, completion: AnimationCompletion) {
194        *self.completion.borrow_mut() = completion;
195    }
196
197    /// Sets the initial value expressions of the animation, applied to the
198    /// target when the instance is initialized.
199    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        // Taken out first: releasing may post the completion of a batch.
205        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    /// The value `t` seconds after the delay, and whether the spring is at
220    /// rest at the final value.
221    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        // At rest within a ten-thousandth of the size of the motion (at least
229        // of one unit), moving slower than that amplitude would at the
230        // frequency of the spring.
231        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        // A paused animation does not end: its controller may resume it.
264        if at_rest && !self.clock.is_paused() {
265            self.leave_clock();
266            self.finished.set(true);
267            // The animation is over: the batches it belongs to may complete.
268            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        // A target that is gone never evaluates the animation again.
278        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}