Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

动画

本节将对 Ranim 中 动画 的实现思路进行讲解。

Eval<T> Trait

一个标准化的动画其实本质上就是一个函数 ,它的输入是一个进度值 ,输出是该动画在对应进度处的结果

这个函数 不仅定义了动画的 求值,同时其内部也包含了求值所需要的 信息。对应到计算机世界,其实也就是 算法数据,而对应到编程语言上也就是 方法数据类型

在由 Rust 实现的 Ranim 中也就是 Eval<T> Trait 和实现了它的类型 T

/// This is the core of any animation, an animation is basically a function on time.
///
/// This represents a normalized animation function for type `T`, which accepts
/// a progress value `alpha` in range [0, 1] and returns the evaluation result in type `T`.
pub trait Eval<T> {
    /// Evaluates at the given progress value `alpha` in range [0, 1].
    fn eval_alpha(&self, alpha: f64) -> T;

    // ...
}

它接受自身的不可变引用和一个进度值作为输入,经过计算,输出一个自身类型的结果。

例 | Static 动画

ranim_core::animation::Static 动画是最基础的,也是唯一一个内置进 ranim-core 的动画:

/// A static animation.
pub struct Static<T>(pub T);

impl<T: Clone> Eval<T> for Static<T> {
    fn eval_alpha(&self, _alpha: f64) -> T {
        self.0.clone()
    }
}

非常简单,其内置的 信息 就是物件本身,其 求值 就是简单的返回相同的物件。

例 | Transform 动画

ranim_anims::transform::Transform 动画为例,其内部包含了物件初始状态和目标状态,以及用于插值的对齐后的初始和目标状态,在 EvalDynamic<T> 的实现中使用内部的数据进行计算求值得到结果:

/// Transform Anim
pub struct Transform<T: TransformRequirement> {
    src: T,
    dst: T,
    aligned_src: T,
    aligned_dst: T,
}

impl<T: TransformRequirement> Eval<T> for Transform<T> {
    fn eval_alpha(&self, alpha: f64) -> T {
        if alpha == 0.0 {
            self.src.clone()
        } else if 0.0 < alpha && alpha < 1.0 {
            self.aligned_src.lerp(&self.aligned_dst, alpha)
        } else if alpha == 1.0 {
            self.dst.clone()
        } else {
            unreachable!()
        }
    }
}

AnimationCell

一个动画还会有很多额外的信息:

  • 开始时间
  • 持续时间
  • 速率函数

在 Ranim 中,这些被信息被表示为一个 AnimationInfo 结构:

/// Info of an animation.
///
/// When [`AnimationInfo::enabled`] is `false`, the animation will not be evaluated.
#[derive(Debug, Clone)]
pub struct AnimationInfo {
    /// The rate function used for evaluating, default value: [`linear`]
    pub rate_func: fn(f64) -> f64,
    /// Start sec, default value: 0.0
    pub start_sec: f64,
    /// The duration seconds, default value: 1.0
    pub duration_secs: f64,
    /// Is enabled, default value: true
    pub enabled: bool,
}

impl Default for AnimationInfo {
    fn default() -> Self {
        Self {
            rate_func: linear,
            start_sec: 0.0,
            duration_secs: 1.0,
            enabled: true,
        }
    }
}

通过这些信息,我们可以将全局的秒映射到局部的 ,并将局部的 映射到内部标准化的

impl AnimationInfo {
    /// Map the global sec to outer alpha
    ///
    /// note that this uses a range_inclusive
    pub fn map_sec_to_alpha(&self, sec: f64) -> Option<f64> {
        if self.range_inclusive().contains(&sec) {
            let alpha = (sec - self.start_sec) / self.duration_secs;
            let alpha = if alpha.is_nan() { 1.0 } else { alpha };
            Some(alpha)
        } else {
            None
        }
    }
    /// Map the outer alpha to inner alpha
    pub fn map_alpha(&self, alpha: f64) -> f64 {
        (self.rate_func)(alpha)
    }
    // ...
}

如此,将以 为输入标准化的动画函数与这些信息结合,也就构造除了一个以秒 为输入的动画函数

在 Ranim 中,这对应着 AnimationCell 结构:

/// A cell of an animation
pub struct AnimationCell<T> {
    inner: Box<dyn Eval<T>>,
    /// The animation info
    pub info: AnimationInfo,
// ...
}

impl<T> Eval<T> for AnimationCell<T> {
    fn eval_alpha(&self, alpha: f64) -> T {
        self.inner.eval_alpha(self.info.map_alpha(alpha))
    }
}

Requirement Trait 模式

相信你注意到了,在实际的动画编写中,我们并没有手动构造任何一个动画结构,而是直接在物件身上调用一个方法来构造 AnimationCell

let vitem_a = // ...;
let vitem_b = // ...;

// let anim = Transform::new(vitem_a, vitem_b).to_animation_cell();
// r.timeline_mut(t_id).play(anim);

r.timeline_mut(t_id).play(vitem_a.clone().transform_to(vitem_b));
let mut vitem_a = // ...;
let vitem_b = // ...;

// let anim = Transform::new(vitem_a, vitem_b).to_animation_cell();
// vitem_a = anim.eval_alpha(1.0);
// r.timeline_mut(t_id).play(anim);

r.timeline_mut(t_id).play(vitem_a.transform_to(vitem_b));

这是 Ranim 动画的一种编程模式,每一个动画都有一个对应的 Requirement Trait:

/// The requirement of [`Transform`]
pub trait TransformRequirement: Alignable + Interpolatable + Clone {}
impl<T: Alignable + Interpolatable + Clone> TransformRequirement for T {}

同时还有一个对应的 Animation Trait,包含了一系列的 Helper 函数,以及为 T: <Requirement Trait> 的实现:

/// The methods to create animations for `T` that satisfies [`TransformRequirement`]
pub trait TransformAnim: TransformRequirement + Sized + 'static {
    /// Create a [`Transform`] anim with a func.
    fn transform<F: Fn(&mut Self)>(&mut self, f: F) -> AnimationCell<Self>;
    /// Create a [`Transform`] anim from src.
    fn transform_from(&mut self, src: Self) -> AnimationCell<Self>;
    /// Create a [`Transform`] anim to dst.
    fn transform_to(&mut self, dst: Self) -> AnimationCell<Self>;
}

impl<T: TransformRequirement + 'static> TransformAnim for T {
    fn transform<F: Fn(&mut T)>(&mut self, f: F) -> AnimationCell<T> {
        let mut dst = self.clone();
        (f)(&mut dst);
        Transform::new(self.clone(), dst)
            .into_animation_cell()
            .with_rate_func(smooth)
            .apply_to(self)
    }
    fn transform_from(&mut self, s: T) -> AnimationCell<T> {
        Transform::new(s, self.clone())
            .into_animation_cell()
            .with_rate_func(smooth)
            .apply_to(self)
    }
    fn transform_to(&mut self, d: T) -> AnimationCell<T> {
        Transform::new(self.clone(), d)
            .into_animation_cell()
            .with_rate_func(smooth)
            .apply_to(self)
    }
}

通过这种模式可以便捷地构造动画,并将动画的效果应用到物件状态上。