动画
本节将对 Ranim 中 动画 的实现思路进行讲解。
EvalDynamic<T>
Trait
一个标准化的动画其实本质上就是一个函数 ,它的输入是一个进度值 ,输出是该动画在对应进度处的结果 :
这个函数 不仅定义了动画的 求值,同时其内部也包含了求值所需要的 信息。对应到计算机世界,其实也就是 算法 和 数据,而对应到编程语言上也就是 方法 和 数据类型。
在由 Rust 实现的 Ranim 中也就是 EvalDynamic<T>
Trait 和实现了它的类型 T
:
pub trait EvalDynamic<T> {
fn eval_alpha(&self, alpha: f64) -> T;
}
它接受自身的不可变引用和一个进度值作为输入,经过计算,输出一个自身类型的结果。
以 Transform
动画为例,其内部包含了物件初始状态和目标状态,以及用于插值的对齐后的初始和目标状态,在 EvalDynamic<T>
的实现中使用内部的数据进行计算求值得到结果:
/// Transform Anim
pub struct Transform<T: TransformRequirement> {
src: T,
dst: T,
aligned_src: T,
aligned_dst: T,
}
impl<T: TransformRequirement> EvalDynamic<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!()
}
}
}
AnimationSpan
有了以进度 为输入标准化的动画函数后,加上持续秒数 、速率函数 ,就可以构造一个以秒 为输入的动画函数 :
在 Ranim 中,这对应着 AnimationSpan
结构:
pub struct AnimationSpan<T> {
pub(crate) evaluator: Evaluator<T>,
pub rate_func: fn(f64) -> f64,
pub duration_secs: f64,
}
impl<T> AnimationSpan<T> {
pub fn eval_alpha(&self, alpha: f64) -> EvalResult<T> {
self.eval_sec(alpha * self.duration_secs)
}
pub fn eval_sec(&self, local_sec: f64) -> EvalResult<T> {
self.evaluator.eval_alpha((self.rate_func)(
(local_sec / self.duration_secs).clamp(0.0, 1.0),
))
}
}
其中的 evaluator: Evaluator<T>
其实就是对 Box<dyn EvalDynamic<T>>
的封装。