///|
/// One value transition in a sequential motion timeline.
pub struct MotionSegment {
start : Double
end : Double
duration : Double
motion : MotionFn
}
///|
/// A deterministic, renderer-independent sequence of motion segments.
pub struct MotionTimeline {
segments : Array[MotionSegment]
}
///|
/// Create an empty motion timeline.
pub fn MotionTimeline::new() -> MotionTimeline {
{ segments: Array::new() }
}
///|
/// Add a segment. Non-positive durations are ignored during playback.
pub fn MotionTimeline::append(
self : MotionTimeline,
start : Double,
end : Double,
duration : Double,
motion : MotionFn,
) -> Unit {
self.segments.push({ start, end, duration, motion })
}
///|
/// Sum the usable durations in this timeline.
pub fn MotionTimeline::duration(self : MotionTimeline) -> Double {
let mut total = 0.0
for segment in self.segments {
if segment.duration > 0.0 {
total = total + segment.duration
}
}
total
}
///|
/// Evaluate the timeline at elapsed time. An empty timeline evaluates to zero.
pub fn MotionTimeline::value_at(self : MotionTimeline, time : Double) -> Double {
let mut elapsed = 0.0
let mut last = 0.0
for segment in self.segments {
last = segment.end
if segment.duration <= 0.0 {
continue
}
if time <= elapsed + segment.duration {
return interpolate(
segment.start,
segment.end,
clamp01((time - elapsed) / segment.duration),
segment.motion,
)
}
elapsed = elapsed + segment.duration
}
last
}
///|
/// Produce timeline values at a fixed frame rate.
pub fn MotionTimeline::frames(
self : MotionTimeline,
fps : Int,
) -> Array[Double] {
let result : Array[Double] = Array::new()
if fps <= 0 {
return result
}
let count = (self.duration() * fps.to_double()).to_int() + 1
for i in 0..