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 的时间线封装并非简单的一层,而是很多层:

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>,
}

在编写动画时的一系列操作(如 forwardplay 等)最后都会转变为对 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