时间线
简单来说,时间线的本质是动画的容器,将若干动画以及其起止时间信息打包在一起也就得到了一条时间线。 一条时间线对应一个物件的全部动画,若干条时间线组合在一起即表示了整个场景的完整动画。
不过因为涉及泛型以及类型擦除,Ranim 的时间线封装并非简单的一层,而是很多层:
classDiagram
class ItemDynTimelines {
+timelines: Vec~ItemDynTimelines~
}
ItemDynTimelines --* DynTimeline
class DynTimeline {
+CameraFrame(Box~dyn AnyTimelineFunc~)
+VisualItem(Box~dyn AnyVisualItemTimelineTrait~)
}
<<Enumeration>> DynTimeline
DynTimeline ..* VisualItemTimelineTrait
DynTimeline ..* TimelineFunc
class ItemTimeline~T~
ItemTimeline --|> TimelineFunc
ItemTimeline ..|> VisualItemTimelineTrait : Where T is VisualItem
class TimelineFunc
<<Trait>> TimelineFunc
VisualItemTimelineTrait --|> TimelineFunc
class VisualItemTimelineTrait
<<Trait>> VisualItemTimelineTrait
ItemTimeline<T>
ItemTimeline
是第一层,它的本质就是一个动画的容器:
/// `ItemTimeline<T>` is used to encode animations for a single type `T`,
/// it contains a list of [`AnimationSpan<T>`] and the corresponding metadata for each span.
pub struct ItemTimeline<T> {
type_name: String,
anims: Vec<(AnimationSpan<T>, std::ops::Range<f64>)>,
// Followings are states use while constructing
cur_sec: f64,
/// The state used for static anim.
state: T,
/// The start time of the planning static anim.
/// When it is true, it means that it is showing.
planning_static_start_sec: Option<f64>,
}
在编写动画时的一系列操作(如 forward
、play
等)最后都会转变为对 ItemTimeline
内部属性的操作,
最终达成的结果就是在其 anims
属性中完成此条时间线所有动画以及其起止时间的编码(即“把动画在时间上放到正确的位置”)。
DynTimeline
DynTimeline
是第二层,用于对 ItemTimeline
进行类型擦除:
/// A type erased [`ItemTimeline<T>`]
///
/// Currently There are two types of Timeline:
/// - [`DynTimeline::CameraFrame`]: Can be created from [`CameraFrame`], has a boxed [`AnyTimelineFunc`] in it.
/// - [`DynTimeline::VisualItem`]: Can be created from [`VisualItem`], has a boxed [`AnyVisualItemTimelineTrait`] in it.
pub enum DynTimeline {
/// A type erased timeline for [`CameraFrame`], its inner is a boxed [`AnyTimelineFunc`].
CameraFrame(Box<dyn AnyTimelineFunc>),
/// A type erased timeline for [`VisualItem`], its inner is a boxed [`AnyVisualItemTimelineTrait`].
VisualItem(Box<dyn AnyVisualItemTimelineTrait>),
}
在场景中,我们会有多个物件,每个物件都有自己的时间线,为了能够遍历时间线进行求值等操作,必须要对时间线进行类型擦除,从而将不同物件的时间线放到一个容器中。
AnyTimelineFunc
就是 Any + TimelineFunc
,带有基础的时间线操作,而 AnyVisualItemTimelineTrait
就是 Any + VisualItemTimelineTrait
,在 TimelineFunc
的基础上额外多了 eval_sec
方法:
/// A visual item timeline, which can eval to `EvalResult<Box<dyn VisualItem>>`.
///
/// This is auto implemented for `ItemTimeline<T>` where `T: Clone + VisualItem + 'static`
pub trait VisualItemTimelineTrait: TimelineFunc {
/// Evaluate the timeline at `target_sec`
fn eval_sec(&self, target_sec: f64) -> Option<(EvalResult<Box<dyn VisualItem>>, usize)>;
}
这样,通过 TimelineId<T>
可以获取对应的 Timeline 并还原类型,而在求值、渲染时没有类型信息,直接使用 Trait 提供的方法进行求值。
ItemDynTimelines
其实到 DynTimeline
已经足够了,但是为了支持“变更同一条 Timeline 的类型”,在 DynTimeline
的基础上又包了第三层:
/// A item timeline which contains multiple [`DynTimeline`], so
/// that it can contains multiple [`ItemTimeline<T>`] in different type of `T`.
pub struct ItemDynTimelines {
id: usize,
timelines: Vec<DynTimeline>,
}
简单来说,在实际操作的时候,永远操作的是最后一个 DynTimeline
,这使得其表现得像是一个 DynTimeline
,不过额外有一个 apply_map
方法,可以使用一个 map_fn: impl FnOnce(T) -> E
来使用最后一个 DynTimeline
的内部状态进行转换,然后再插入一个新的 DynTimeline
。