Skip to main content

ranim_core/
animation.rs

1//! Pure animation evaluation and hierarchical type-erased composition.
2
3use std::{any::type_name, fmt::Debug, ops::Range};
4
5use crate::{
6    core_item::{AnyExtractCoreItem, DynItem},
7    utils::rate_functions::linear,
8};
9
10/// A normalized animation evaluator.
11///
12/// Functional (closed-form) segments implement [`Eval::eval_alpha`]; the other
13/// methods default appropriately. Iterative (stateful) segments implement
14/// [`Eval::sample`]/[`Eval::reset`]/[`Eval::step`] instead — `eval_alpha` has
15/// no closed form and its default panics if called.
16pub trait Eval {
17    /// Value produced by this evaluator.
18    type Output;
19
20    /// Evaluate the animation at a normalized progress in `[0, 1]`.
21    /// Functional segments implement this; iterative segments have no closed
22    /// form and the default panics (the runtime drives them via `sample`).
23    fn eval_alpha(&self, _alpha: f64) -> Self::Output {
24        unreachable!("iterative segment has no closed form; drive it via `sample`/`step`")
25    }
26
27    /// Sample the current state at the given time context.
28    /// Functional default = eval_alpha(time.alpha); iterative segments
29    /// implement this to return their current state.
30    fn sample(&self, time: &SegmentTime) -> Self::Output {
31        self.eval_alpha(time.alpha)
32    }
33
34    /// Reset to the segment's initial state (deterministic contract: no wall
35    /// clock, no unseeded RNG). No-op by default.
36    fn reset(&mut self) {}
37
38    /// Advance one logic step (or substep); `time.local_delta_secs` is the
39    /// integration step. No-op by default (functional segments are stepped as
40    /// no-ops for free; sampling is unaffected).
41    fn step(&mut self, _time: &SegmentTime) {}
42
43    /// Apply the final state and return this evaluator.
44    fn apply_to(self, item: &mut Self::Output) -> Self
45    where
46        Self: Sized,
47    {
48        self.apply_alpha_to(item, 1.0)
49    }
50
51    /// Apply a sampled state and return this evaluator.
52    fn apply_alpha_to(self, item: &mut Self::Output, alpha: f64) -> Self
53    where
54        Self: Sized,
55    {
56        *item = self.eval_alpha(alpha);
57        self
58    }
59}
60
61impl<T, F> Eval for F
62where
63    F: Fn(f64) -> T,
64{
65    type Output = T;
66
67    fn eval_alpha(&self, alpha: f64) -> Self::Output {
68        (self)(alpha)
69    }
70}
71
72/// A complete time context for one evaluation/step of an animation segment.
73///
74/// All time values are in seconds (`_secs`); `alpha` is normalized to `[0, 1]`.
75/// Deltas are only meaningful during [`Eval::step`]; during sampling they are zero.
76#[derive(Debug, Clone, Copy, Default)]
77pub struct SegmentTime {
78    /// Global scene time `t` at the current logic tick (seconds).
79    pub global_secs: f64,
80    /// Global logic step length, stable (`= 1 / logic_fps`).
81    pub global_delta_secs: f64,
82    /// Segment start `s` in its parent's coordinates (seconds).
83    pub start_secs: f64,
84    /// Segment duration `D` (seconds).
85    pub duration_secs: f64,
86    /// Rate-warped local time `u(t) = D · r((t−s)/D)` (seconds).
87    pub local_secs: f64,
88    /// Local increment `Δu` for this step, varies with the rate function (seconds).
89    pub local_delta_secs: f64,
90    /// Normalized progress `alpha = local_secs / D`.
91    pub alpha: f64,
92    /// Current render frame index (for frame-coupled segments).
93    pub render_frame: u64,
94    /// Whether this logic step completes a render frame (for frame-coupled segments).
95    pub is_render_frame_boundary: bool,
96}
97
98/// An auto implemented trait for erasing Evaluator<Output = T> where T: AnyExtractCoreItem
99trait EvalDyn {
100    fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>);
101
102    /// Sample at a full time context. Default rides the pure alpha path for
103    /// containers and functional leaves; iterative leaves override it via the
104    /// blanket impl below.
105    fn sample_dyn(&self, time: &SegmentTime, output: &mut Vec<DynItem>) {
106        self.eval_alpha_dyn_into(time.alpha, output);
107    }
108
109    /// Reset the internal state. No-op by default.
110    fn reset_dyn(&mut self) {}
111
112    /// Advance one logic step. No-op by default (functional segments and
113    /// containers without iterative leaves step for free).
114    fn step_dyn(&mut self, _time: &SegmentTime) {}
115
116    fn info_kind(&self) -> AnimationInfoKind {
117        AnimationInfoKind::Eval
118    }
119
120    fn content_duration_secs(&self) -> f64 {
121        1.0
122    }
123
124    fn child_infos(&self) -> Vec<AnimationInfo> {
125        Vec::new()
126    }
127}
128
129struct StaticDynItems(Vec<DynItem>);
130
131impl EvalDyn for StaticDynItems {
132    fn eval_alpha_dyn_into(&self, _alpha: f64, output: &mut Vec<DynItem>) {
133        output.extend(self.0.iter().cloned());
134    }
135
136    fn info_kind(&self) -> AnimationInfoKind {
137        AnimationInfoKind::Static
138    }
139
140    fn content_duration_secs(&self) -> f64 {
141        0.0
142    }
143}
144
145impl<E> EvalDyn for E
146where
147    E: Eval,
148    E::Output: AnyExtractCoreItem,
149{
150    fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
151        // Pure path (eval_at_sec) only supports functional segments; iterative
152        // segments have no closed form and must be driven via SceneEvaluator.
153        output.push(DynItem(Box::new(self.eval_alpha(alpha))));
154    }
155
156    fn sample_dyn(&self, time: &SegmentTime, output: &mut Vec<DynItem>) {
157        output.push(DynItem(Box::new(self.sample(time))));
158    }
159
160    fn reset_dyn(&mut self) {
161        Eval::reset(self);
162    }
163
164    fn step_dyn(&mut self, time: &SegmentTime) {
165        Eval::step(self, time);
166    }
167}
168
169/// Runtime animation content category used by preview tooling.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum AnimationInfoKind {
172    /// A typed evaluator without animation children.
173    Eval,
174    /// A sequential animation container.
175    Sequence,
176    /// An overlay animation container.
177    Stack,
178    /// A captured, type-erased static output batch.
179    Static,
180}
181
182/// Hierarchical runtime animation information used by preview tooling.
183#[derive(Clone)]
184pub struct AnimationInfo {
185    /// Concrete evaluator or container type name.
186    pub anim_name: String,
187    /// Runtime content category.
188    pub kind: AnimationInfoKind,
189    /// Time range in the parent content's local coordinates.
190    pub range: Range<f64>,
191    /// Duration of this node's inner content before outer timing is applied.
192    pub content_duration_secs: f64,
193    /// Time remapping function applied by this node.
194    pub rate_func: fn(f64) -> f64,
195    /// Whether this node contributes values during evaluation.
196    pub enabled: bool,
197    /// Direct animation children in this node's content coordinates.
198    pub children: Vec<AnimationInfo>,
199}
200
201/// A single runtime animation node in its parent's time coordinates.
202pub struct AnimationCell {
203    inner: Box<dyn EvalDyn>,
204    rate_func: fn(f64) -> f64,
205    time_range: Range<f64>,
206    enabled: bool,
207    anim_name: &'static str,
208    /// Whether this cell's inner state has been reset for the current run.
209    entered: bool,
210}
211
212impl AnimationCell {
213    /// Global or parent-relative time range, depending on its containing plan.
214    pub fn time_range(&self) -> Range<f64> {
215        self.time_range.clone()
216    }
217
218    /// Duration in seconds.
219    pub fn duration_secs(&self) -> f64 {
220        self.time_range.end - self.time_range.start
221    }
222
223    fn shift_by(&mut self, offset_sec: f64) {
224        self.time_range.start += offset_sec;
225        self.time_range.end += offset_sec;
226    }
227
228    /// Whether this clip contributes a value.
229    pub fn enabled(&self) -> bool {
230        self.enabled
231    }
232
233    /// Concrete evaluator type name captured before erasure.
234    pub fn anim_name(&self) -> &str {
235        self.anim_name
236    }
237
238    /// Whether the given scene time is inside this clip's inclusive range.
239    pub fn active_at(&self, sec: f64) -> bool {
240        sec >= self.time_range.start && sec <= self.time_range.end
241    }
242
243    /// Compute the time context in this cell's local coordinates.
244    fn local_time(&self, sec: f64, prev_sec: f64) -> SegmentTime {
245        let duration = self.duration_secs();
246        let alpha = if duration == 0.0 {
247            1.0
248        } else {
249            (sec - self.time_range.start) / duration
250        };
251        let prev_alpha = if duration == 0.0 {
252            1.0
253        } else {
254            (prev_sec - self.time_range.start) / duration
255        };
256        let rate_alpha = (self.rate_func)(alpha);
257        let rate_prev = (self.rate_func)(prev_alpha);
258        SegmentTime {
259            global_secs: sec,
260            global_delta_secs: sec - prev_sec,
261            start_secs: self.time_range.start,
262            duration_secs: duration,
263            local_secs: rate_alpha * duration,
264            local_delta_secs: (rate_alpha - rate_prev) * duration,
265            alpha: rate_alpha,
266            render_frame: 0,
267            is_render_frame_boundary: false,
268        }
269    }
270
271    /// Mark this clip as not yet entered (used by `SceneEvaluator::seek`).
272    pub(crate) fn reset_entered(&mut self) {
273        self.entered = false;
274    }
275
276    /// Sample this clip at a scene time (pure; no tick advancement).
277    pub(crate) fn sample_at_sec(&self, sec: f64, output: &mut Vec<DynItem>) {
278        if !self.active_at(sec) || !self.enabled {
279            return;
280        }
281        let time = self.local_time(sec, sec);
282        self.inner.sample_dyn(&time, output);
283    }
284
285    /// Advance this clip's inner state along the logic grid.
286    ///
287    /// Resets the inner state on first activation (deterministic replay), then
288    /// steps it with the rate-warped local delta. Functional leaves and
289    /// containers without iterative content step as no-ops.
290    pub(crate) fn step_at_sec(&mut self, sec: f64, prev_sec: f64) {
291        if !self.active_at(sec) || !self.enabled {
292            return;
293        }
294        if !self.entered {
295            self.inner.reset_dyn();
296            self.entered = true;
297        }
298        let time = self.local_time(sec, prev_sec);
299        self.inner.step_dyn(&time);
300    }
301
302    /// Append normalized evaluation results after applying this node's rate function.
303    pub fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
304        if self.enabled {
305            self.inner
306                .eval_alpha_dyn_into((self.rate_func)(alpha), output);
307        }
308    }
309
310    /// Evaluate normalized progress into a flat collection of scene items.
311    pub fn eval_alpha_dyn(&self, alpha: f64) -> Vec<DynItem> {
312        let mut output = Vec::new();
313        self.eval_alpha_dyn_into(alpha, &mut output);
314        output
315    }
316
317    /// Evaluate at a time contained in this clip's inclusive range.
318    pub fn eval_at_sec(&self, sec: f64) -> Option<Vec<DynItem>> {
319        if sec < self.time_range.start || sec > self.time_range.end {
320            return None;
321        }
322        let duration = self.duration_secs();
323        let alpha = if duration == 0.0 {
324            1.0
325        } else {
326            (sec - self.time_range.start) / duration
327        };
328        Some(self.eval_alpha_dyn(alpha))
329    }
330
331    fn contains_sec(&self, sec: f64, parent_duration: f64) -> bool {
332        self.time_range.contains(&sec) || (sec == parent_duration && sec == self.time_range.end)
333    }
334
335    pub(crate) fn animation_info(&self) -> AnimationInfo {
336        AnimationInfo {
337            anim_name: self.anim_name.to_string(),
338            kind: self.inner.info_kind(),
339            range: self.time_range.clone(),
340            content_duration_secs: self.inner.content_duration_secs(),
341            rate_func: self.rate_func,
342            enabled: self.enabled,
343            children: self.inner.child_infos(),
344        }
345    }
346}
347
348/// A statically typed animation definition that can be lowered into a runtime animation.
349pub trait Animation: Sized {
350    /// Lower this definition into its local runtime representation.
351    fn build(self) -> AnimationCell;
352}
353
354/// Capability for animation definitions that have not been fixed in parent time coordinates.
355///
356/// This trait is used to constrain the anims to be inserted into [`AnimSequence`].
357/// Only anims those are not placed can be inserted into it.
358pub trait Placeable: Animation {
359    /// Place this definition at an offset in its parent's local time coordinates.
360    fn at(self, offset_sec: f64) -> At<Self> {
361        At {
362            inner: self,
363            offset_sec,
364        }
365    }
366}
367
368/// Playback parameter builders for animations that have not been placed yet.
369pub trait AnimationExt: Placeable {
370    /// Change the animation's rate function.
371    fn with_rate_func(self, rate_func: fn(f64) -> f64) -> Paramed<Self> {
372        Paramed::new(self).with_rate_func(rate_func)
373    }
374
375    /// Change the animation's duration.
376    fn with_duration(self, duration_secs: f64) -> Paramed<Self> {
377        Paramed::new(self).with_duration(duration_secs)
378    }
379
380    /// Enable or disable this animation's output.
381    fn with_enabled(self, enabled: bool) -> Paramed<Self> {
382        Paramed::new(self).with_enabled(enabled)
383    }
384}
385
386impl<A: Placeable> AnimationExt for A {}
387
388impl<E> Placeable for E
389where
390    E: Eval + 'static,
391    E::Output: AnyExtractCoreItem,
392{
393}
394impl<E> Animation for E
395where
396    E: Eval + 'static,
397    E::Output: AnyExtractCoreItem,
398{
399    fn build(self) -> AnimationCell {
400        AnimationCell {
401            anim_name: type_name::<E>(),
402            inner: Box::new(self),
403            rate_func: linear,
404            time_range: 0.0..1.0,
405            enabled: true,
406            entered: false,
407        }
408    }
409}
410
411/// Playback parameters applied to an animation definition.
412#[derive(Debug, Clone)]
413pub(crate) struct AnimationParam {
414    /// Time remapping function.
415    pub rate_func: fn(f64) -> f64,
416    /// Optional duration override in seconds.
417    pub duration_secs: Option<f64>,
418    /// Whether this animation contributes a value.
419    pub enabled: bool,
420}
421
422impl Default for AnimationParam {
423    fn default() -> Self {
424        Self {
425            rate_func: linear,
426            duration_secs: None,
427            enabled: true,
428        }
429    }
430}
431
432/// An animation definition with overridden playback parameters.
433pub struct Paramed<A> {
434    inner: A,
435    param: AnimationParam,
436}
437
438impl<A> Paramed<A> {
439    /// Wrap an animation without overriding its duration.
440    pub(crate) fn new(inner: A) -> Self {
441        Self {
442            inner,
443            param: AnimationParam::default(),
444        }
445    }
446
447    /// Change the animation's rate function.
448    pub fn with_rate_func(mut self, rate_func: fn(f64) -> f64) -> Self {
449        self.param.rate_func = rate_func;
450        self
451    }
452
453    /// Change the animation's duration.
454    pub fn with_duration(mut self, duration_secs: f64) -> Self {
455        assert_valid_duration(duration_secs);
456        self.param.duration_secs = Some(duration_secs);
457        self
458    }
459
460    /// Enable or disable this animation's output.
461    pub fn with_enabled(mut self, enabled: bool) -> Self {
462        self.param.enabled = enabled;
463        self
464    }
465}
466
467impl<A: Placeable + 'static> Placeable for Paramed<A> {}
468impl<A: Placeable + 'static> Animation for Paramed<A> {
469    fn build(self) -> AnimationCell {
470        let mut cell = self.inner.build();
471        if let Some(duration_secs) = self.param.duration_secs {
472            cell.time_range = 0.0..duration_secs;
473        }
474        cell.rate_func = self.param.rate_func;
475        cell.enabled = self.param.enabled;
476        cell.anim_name = type_name::<A>();
477        cell
478    }
479}
480
481/// An animation fixed at an offset in its parent's time coordinates.
482///
483/// This is a terminal placement entry: it implements [`Animation`] but not
484/// [`Placeable`], so playback parameters must be configured before calling
485/// [`Placeable::at`].
486pub struct At<A> {
487    inner: A,
488    offset_sec: f64,
489}
490
491impl<A: Animation> Animation for At<A> {
492    fn build(self) -> AnimationCell {
493        let mut animation = self.inner.build();
494        animation.shift_by(self.offset_sec);
495        animation
496    }
497}
498
499fn assert_valid_duration(duration_secs: f64) {
500    assert!(
501        duration_secs.is_finite() && duration_secs >= 0.0,
502        "animation duration must be finite and non-negative"
503    );
504}
505
506/// Map a cell-local time context into a container's content coordinates.
507///
508/// The cell wrapping the container applies its own rate function; the content
509/// position is `content_duration · (local_secs / cell_duration)`, mirroring the
510/// pure path's `content_secs = cursor · rate(alpha)` mapping.
511fn map_content_time(time: &SegmentTime, content_duration: f64) -> (f64, f64) {
512    let cell_duration = time.duration_secs;
513    if cell_duration <= 0.0 {
514        return (0.0, 0.0);
515    }
516    let content_sec = content_duration * (time.local_secs / cell_duration);
517    let prev_content_sec =
518        content_duration * ((time.local_secs - time.local_delta_secs) / cell_duration);
519    (content_sec, prev_content_sec)
520}
521
522/// Dynamic sequential animation container.
523///
524/// `push` erases each direct child's Rust type while retaining its runtime
525/// composition hierarchy in this sequence's local coordinates.
526#[derive(Default)]
527pub struct AnimSequence {
528    animations: Vec<AnimationCell>,
529    cursor_sec: f64,
530}
531
532impl AnimSequence {
533    /// Create an empty sequence.
534    pub fn new() -> Self {
535        Self::default()
536    }
537
538    fn eval_at_sec_into(&self, target_sec: f64, output: &mut Vec<DynItem>) {
539        if let Some(animation) = self
540            .animations
541            .iter()
542            .rev()
543            .find(|animation| animation.contains_sec(target_sec, self.cursor_sec))
544        {
545            animation.sample_at_sec(target_sec, output);
546        }
547    }
548
549    /// Append an animation at the current cursor and advance by its local extent.
550    pub fn push<A: Placeable + 'static>(&mut self, animation: A) -> &mut Self {
551        let mut animation = animation.build();
552        let duration_secs = animation.duration_secs();
553        animation.shift_by(self.cursor_sec);
554        self.animations.push(animation);
555        self.cursor_sec += duration_secs;
556        self
557    }
558
559    /// Append another sequence's direct children at the current cursor.
560    pub fn extend(&mut self, mut sequence: AnimSequence) -> &mut Self {
561        for animation in &mut sequence.animations {
562            animation.shift_by(self.cursor_sec);
563        }
564        self.animations.extend(sequence.animations);
565        self.cursor_sec += sequence.cursor_sec;
566        self
567    }
568
569    /// Advance the cursor without adding an animation.
570    pub fn forward(&mut self, secs: f64) -> &mut Self {
571        assert!(
572            secs.is_finite() && secs >= 0.0,
573            "forward duration must be finite and non-negative"
574        );
575        self.cursor_sec += secs;
576        self
577    }
578
579    /// Advance the cursor to `target_sec` without adding an animation.
580    pub fn forward_to(&mut self, target_sec: f64) -> &mut Self {
581        assert!(
582            target_sec.is_finite() && target_sec >= 0.0,
583            "forward target must be finite and non-negative"
584        );
585        if target_sec > self.cursor_sec {
586            self.forward(target_sec - self.cursor_sec);
587        }
588        self
589    }
590
591    /// Advance the cursor while holding the state immediately before it.
592    pub fn hold(&mut self, secs: f64) -> &mut Self {
593        assert!(
594            secs.is_finite() && secs >= 0.0,
595            "hold duration must be finite and non-negative"
596        );
597        if secs == 0.0 {
598            return self;
599        }
600
601        let mut state = Vec::new();
602        self.eval_at_sec_into(self.cursor_sec, &mut state);
603
604        if !state.is_empty() {
605            self.animations.push(AnimationCell {
606                inner: Box::new(StaticDynItems(state)),
607                rate_func: linear,
608                time_range: self.cursor_sec..self.cursor_sec + secs,
609                enabled: true,
610                anim_name: type_name::<StaticDynItems>(),
611                entered: false,
612            });
613        }
614        self.cursor_sec += secs;
615        self
616    }
617
618    /// Advance the cursor to `target_sec` while holding its current state.
619    pub fn hold_to(&mut self, target_sec: f64) -> &mut Self {
620        assert!(
621            target_sec.is_finite() && target_sec >= 0.0,
622            "hold target must be finite and non-negative"
623        );
624        if target_sec > self.cursor_sec {
625            self.hold(target_sec - self.cursor_sec);
626        }
627        self
628    }
629
630    /// Current cursor position.
631    pub fn cursor_sec(&self) -> f64 {
632        self.cursor_sec
633    }
634
635    /// Current sequence duration.
636    pub fn duration_secs(&self) -> f64 {
637        self.cursor_sec
638    }
639
640    /// Borrow the direct child animations in local sequence coordinates.
641    pub fn built_animations(&self) -> &[AnimationCell] {
642        &self.animations
643    }
644
645    /// Consume this sequence into its direct child animations.
646    pub fn into_built_animations(self) -> Vec<AnimationCell> {
647        self.animations
648    }
649}
650
651impl Placeable for AnimSequence {}
652impl Animation for AnimSequence {
653    fn build(self) -> AnimationCell {
654        let duration_secs = self.cursor_sec;
655        AnimationCell {
656            inner: Box::new(self),
657            rate_func: linear,
658            time_range: 0.0..duration_secs,
659            enabled: true,
660            anim_name: type_name::<Self>(),
661            entered: false,
662        }
663    }
664}
665
666impl EvalDyn for AnimSequence {
667    fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
668        self.eval_at_sec_into(self.cursor_sec * alpha, output);
669    }
670
671    fn step_dyn(&mut self, time: &SegmentTime) {
672        // Map the time from the wrapping cell's local coordinates into this
673        // sequence's content coordinates.
674        let (content_sec, prev_content_sec) = map_content_time(time, self.cursor_sec);
675        if let Some(child) = self
676            .animations
677            .iter_mut()
678            .rev()
679            .find(|child| child.contains_sec(content_sec, self.cursor_sec))
680        {
681            child.step_at_sec(content_sec, prev_content_sec);
682        }
683    }
684
685    fn info_kind(&self) -> AnimationInfoKind {
686        AnimationInfoKind::Sequence
687    }
688
689    fn content_duration_secs(&self) -> f64 {
690        self.cursor_sec
691    }
692
693    fn child_infos(&self) -> Vec<AnimationInfo> {
694        self.animations
695            .iter()
696            .map(AnimationCell::animation_info)
697            .collect()
698    }
699}
700
701/// Construct an [`AnimSequence`] by playing each animation in order.
702#[macro_export]
703macro_rules! seq {
704    ($($animation:expr),* $(,)?) => {
705        {
706            #[allow(unused_mut)]
707            let mut sequence = $crate::animation::AnimSequence::new();
708            $(sequence.push($animation);)*
709            sequence
710        }
711    };
712}
713
714/// Dynamic overlay animation container.
715///
716/// Unlike [`AnimSequence`], every pushed animation keeps its own local start
717/// time and the stack duration is the maximum child extent.
718#[derive(Default)]
719pub struct AnimStack {
720    animations: Vec<AnimationCell>,
721    duration_secs: f64,
722}
723
724impl AnimStack {
725    /// Create an empty dynamic stack.
726    pub fn new() -> Self {
727        Self::default()
728    }
729
730    fn eval_at_sec_into(&self, target_sec: f64, output: &mut Vec<DynItem>) {
731        for animation in &self.animations {
732            if animation.contains_sec(target_sec, self.duration_secs) {
733                animation.sample_at_sec(target_sec, output);
734            }
735        }
736    }
737
738    /// Add an animation without advancing the other children.
739    pub fn push<A: Animation + 'static>(&mut self, animation: A) -> &mut Self {
740        let animation = animation.build();
741        self.duration_secs = self.duration_secs.max(animation.time_range.end);
742        self.animations.push(animation);
743        self
744    }
745
746    /// Add all direct child animations from another dynamic stack.
747    pub fn extend(&mut self, stack: AnimStack) -> &mut Self {
748        self.duration_secs = self.duration_secs.max(stack.duration_secs);
749        self.animations.extend(stack.animations);
750        self
751    }
752
753    /// Current maximum child extent.
754    pub fn duration_secs(&self) -> f64 {
755        self.duration_secs
756    }
757
758    /// Borrow the direct child animations in local stack coordinates.
759    pub fn built_animations(&self) -> &[AnimationCell] {
760        &self.animations
761    }
762
763    /// Consume this stack into its direct child animations.
764    pub fn into_built_animations(self) -> Vec<AnimationCell> {
765        self.animations
766    }
767}
768
769impl Placeable for AnimStack {}
770impl Animation for AnimStack {
771    fn build(self) -> AnimationCell {
772        let duration_secs = self.duration_secs;
773        AnimationCell {
774            inner: Box::new(self),
775            rate_func: linear,
776            time_range: 0.0..duration_secs,
777            enabled: true,
778            anim_name: type_name::<Self>(),
779            entered: false,
780        }
781    }
782}
783
784impl EvalDyn for AnimStack {
785    fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
786        self.eval_at_sec_into(self.duration_secs * alpha, output);
787    }
788
789    fn step_dyn(&mut self, time: &SegmentTime) {
790        let (content_sec, prev_content_sec) = map_content_time(time, self.duration_secs);
791        for child in &mut self.animations {
792            if child.contains_sec(content_sec, self.duration_secs) {
793                child.step_at_sec(content_sec, prev_content_sec);
794            }
795        }
796    }
797
798    fn info_kind(&self) -> AnimationInfoKind {
799        AnimationInfoKind::Stack
800    }
801
802    fn content_duration_secs(&self) -> f64 {
803        self.duration_secs
804    }
805
806    fn child_infos(&self) -> Vec<AnimationInfo> {
807        self.animations
808            .iter()
809            .map(AnimationCell::animation_info)
810            .collect()
811    }
812}
813
814/// Construct an [`AnimStack`] by pushing each animation at the same origin.
815#[macro_export]
816macro_rules! stack {
817    ($($animation:expr),* $(,)?) => {
818        {
819            #[allow(unused_mut)]
820            let mut stack = $crate::animation::AnimStack::new();
821            $(stack.push($animation);)*
822            stack
823        }
824    };
825}
826
827/// Requirement for [`StaticAnim`].
828pub trait StaticAnimRequirement: Clone + AnyExtractCoreItem {}
829
830impl<T: Clone + AnyExtractCoreItem> StaticAnimRequirement for T {}
831
832/// Convenience methods for zero-duration static animations.
833pub trait StaticAnim: StaticAnimRequirement + Sized {
834    /// Show this value.
835    fn show(&self) -> Paramed<Static<Self>>;
836    /// Hide this value.
837    fn hide(&self) -> Paramed<Static<Self>>;
838}
839
840impl<T: StaticAnimRequirement + 'static> StaticAnim for T {
841    fn show(&self) -> Paramed<Static<Self>> {
842        Static(self.clone()).with_duration(0.0)
843    }
844
845    fn hide(&self) -> Paramed<Static<Self>> {
846        Static(self.clone()).with_enabled(false).with_duration(0.0)
847    }
848}
849
850/// A constant evaluator.
851pub struct Static<T: Clone>(pub T);
852
853impl<T: Clone> Eval for Static<T> {
854    type Output = T;
855
856    fn eval_alpha(&self, _alpha: f64) -> Self::Output {
857        self.0.clone()
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use crate::{Extract, core_item::CoreItem, core_item::vitem::VItem};
865
866    fn leaf(x: f32, duration: f64) -> impl Placeable {
867        (move |_alpha| {
868            let mut item = VItem::default();
869            item.points[0].x = x;
870            item
871        })
872        .with_duration(duration)
873    }
874
875    fn progress_leaf(offset: f32) -> impl Placeable {
876        move |alpha| {
877            let mut item = VItem::default();
878            item.points[0].x = offset + alpha as f32;
879            item
880        }
881    }
882
883    fn evaluated_xs(items: Vec<DynItem>) -> Vec<f32> {
884        items
885            .into_iter()
886            .flat_map(|item| item.extract())
887            .filter_map(|item| match item {
888                CoreItem::VItem(item) => Some(item.points[0].x),
889                CoreItem::CameraFrame(_) | CoreItem::MeshItem(_) => None,
890            })
891            .collect()
892    }
893
894    #[test]
895    fn at_offsets_the_built_animation() {
896        let animation = leaf(1.0, 2.0).at(3.0).build();
897        assert_eq!(animation.time_range(), 3.0..5.0);
898    }
899
900    #[test]
901    fn eval_uses_linear_one_second_defaults() {
902        let animation = Static(VItem::default()).build();
903        assert_eq!(animation.time_range(), 0.0..1.0);
904    }
905
906    #[test]
907    fn parametrized_sequence_remaps_the_group_timeline() {
908        use crate::utils::rate_functions::ease_in_quad;
909
910        let animation = seq![progress_leaf(0.0), progress_leaf(10.0)]
911            .with_duration(4.0)
912            .with_rate_func(ease_in_quad);
913        let animation = animation.build();
914
915        assert_eq!(animation.time_range(), 0.0..4.0);
916        let items = animation.eval_at_sec(2.0).unwrap();
917        assert_eq!(evaluated_xs(items), vec![0.5]);
918        let info = animation.animation_info();
919        assert_eq!(info.range, 0.0..4.0);
920        assert_eq!(info.content_duration_secs, 2.0);
921        assert_eq!(info.children.len(), 2);
922        assert_eq!(info.children[0].range, 0.0..1.0);
923        assert_eq!(info.children[1].range, 1.0..2.0);
924    }
925
926    #[test]
927    fn seq_uses_child_durations() {
928        let sequence = seq![leaf(1.0, 2.0), leaf(2.0, 3.0)];
929        assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
930        assert_eq!(sequence.built_animations()[1].time_range(), 2.0..5.0);
931        assert_eq!(sequence.at(5.0).build().time_range(), 5.0..10.0);
932    }
933
934    #[test]
935    fn stack_accepts_plain_and_positioned_children() {
936        let animation = stack![leaf(1.0, 2.0), leaf(2.0, 3.0).at(1.0)];
937        assert_eq!(animation.duration_secs(), 4.0);
938        assert_eq!(animation.built_animations()[0].time_range(), 0.0..2.0);
939        assert_eq!(animation.built_animations()[1].time_range(), 1.0..4.0);
940        assert_eq!(animation.at(10.0).build().time_range(), 10.0..14.0);
941    }
942
943    #[test]
944    fn composition_macros_build_dynamic_containers_without_an_arity_limit() {
945        let empty_sequence: AnimSequence = seq![];
946        let empty_stack: AnimStack = stack![];
947        assert_eq!(empty_sequence.duration_secs(), 0.0);
948        assert_eq!(empty_stack.duration_secs(), 0.0);
949
950        let sequence: AnimSequence = seq![
951            leaf(1.0, 1.0),
952            leaf(2.0, 1.0),
953            leaf(3.0, 1.0),
954            leaf(4.0, 1.0),
955            leaf(5.0, 1.0),
956            leaf(6.0, 1.0),
957            leaf(7.0, 1.0),
958            leaf(8.0, 1.0),
959            leaf(9.0, 1.0),
960        ];
961        assert_eq!(sequence.duration_secs(), 9.0);
962        assert_eq!(sequence.built_animations().len(), 9);
963
964        let stack: AnimStack = stack![
965            leaf(1.0, 1.0),
966            leaf(2.0, 2.0),
967            leaf(3.0, 3.0),
968            leaf(4.0, 4.0),
969            leaf(5.0, 5.0),
970            leaf(6.0, 6.0),
971            leaf(7.0, 7.0),
972            leaf(8.0, 8.0),
973            leaf(9.0, 9.0),
974        ];
975        assert_eq!(stack.duration_secs(), 9.0);
976        assert_eq!(stack.built_animations().len(), 9);
977    }
978
979    #[test]
980    fn sequence_can_be_repositioned_after_erasure() {
981        let mut sequence = AnimSequence::new();
982        sequence
983            .push(leaf(1.0, 2.0))
984            .forward(1.0)
985            .push(leaf(2.0, 1.0));
986        let animation = sequence.at(10.0).build();
987        let info = animation.animation_info();
988        assert_eq!(info.range, 10.0..14.0);
989        assert_eq!(info.children[0].range, 0.0..2.0);
990        assert_eq!(info.children[1].range, 3.0..4.0);
991    }
992
993    #[test]
994    fn hold_samples_only_animations_active_before_the_cursor() {
995        let mut sequence = AnimSequence::new();
996        sequence
997            .push(stack![leaf(1.0, 1.0), leaf(2.0, 2.0)])
998            .hold(1.0);
999
1000        assert_eq!(sequence.built_animations().len(), 2);
1001        let held = sequence.built_animations()[1].eval_at_sec(2.5).unwrap();
1002        assert_eq!(evaluated_xs(held), vec![2.0]);
1003    }
1004
1005    #[test]
1006    fn repeated_hold_creates_adjacent_static_animations() {
1007        let mut sequence = AnimSequence::new();
1008        sequence.push(leaf(3.0, 1.0)).hold(1.0).hold(2.0);
1009
1010        assert_eq!(sequence.cursor_sec(), 4.0);
1011        assert_eq!(sequence.built_animations().len(), 3);
1012        assert_eq!(sequence.built_animations()[1].time_range(), 1.0..2.0);
1013        assert_eq!(sequence.built_animations()[2].time_range(), 2.0..4.0);
1014        let held = sequence.built_animations()[2].eval_at_sec(3.5).unwrap();
1015        assert_eq!(evaluated_xs(held), vec![3.0]);
1016    }
1017
1018    #[test]
1019    fn repeated_hold_replays_dyn_items_without_nesting_the_output_batch() {
1020        let mut sequence = AnimSequence::new();
1021        sequence
1022            .push(stack![leaf(1.0, 1.0), leaf(2.0, 1.0)])
1023            .hold(1.0)
1024            .hold(1.0);
1025
1026        let first_hold = sequence.built_animations()[1].eval_at_sec(1.5).unwrap();
1027        let second_hold = sequence.built_animations()[2].eval_at_sec(2.5).unwrap();
1028
1029        assert_eq!(first_hold.len(), 2);
1030        assert_eq!(second_hold.len(), 2);
1031        assert_eq!(evaluated_xs(second_hold), vec![1.0, 2.0]);
1032    }
1033
1034    #[test]
1035    fn forward_does_not_hold_the_previous_state() {
1036        let mut sequence = AnimSequence::new();
1037        sequence.push(leaf(4.0, 1.0)).forward(1.0).hold(1.0);
1038
1039        assert_eq!(sequence.cursor_sec(), 3.0);
1040        assert_eq!(sequence.built_animations().len(), 1);
1041    }
1042
1043    #[test]
1044    fn hold_uses_the_sequences_final_evaluation() {
1045        let mut shown = VItem::default();
1046        shown.points[0].x = 5.0;
1047
1048        let mut hidden = AnimSequence::new();
1049        hidden.push(leaf(1.0, 1.0)).push(shown.hide()).hold(1.0);
1050        assert_eq!(hidden.built_animations().len(), 2);
1051        assert!(
1052            hidden.built_animations()[1]
1053                .eval_at_sec(1.0)
1054                .unwrap()
1055                .is_empty()
1056        );
1057
1058        let mut restored = AnimSequence::new();
1059        restored.push(leaf(1.0, 1.0)).push(shown.show()).hold(1.0);
1060        let held = restored.built_animations()[2].eval_at_sec(1.5).unwrap();
1061        assert_eq!(evaluated_xs(held), vec![5.0]);
1062    }
1063
1064    #[test]
1065    fn nested_sequences_keep_their_own_final_evaluation() {
1066        let mut shown = VItem::default();
1067        shown.points[0].x = 7.0;
1068
1069        let inner = seq![leaf(1.0, 1.0), shown.show()];
1070        let mut outer = AnimSequence::new();
1071        outer.push(inner).hold(1.0);
1072
1073        assert_eq!(outer.built_animations().len(), 2);
1074        let held = outer.built_animations()[1].eval_at_sec(1.5).unwrap();
1075        assert_eq!(evaluated_xs(held), vec![7.0]);
1076
1077        let hidden_inner = seq![leaf(1.0, 1.0), shown.hide()];
1078        let mut hidden_outer = AnimSequence::new();
1079        hidden_outer.push(hidden_inner).hold(1.0);
1080        assert_eq!(hidden_outer.built_animations().len(), 1);
1081    }
1082
1083    #[test]
1084    fn dynamic_stack_keeps_children_at_the_same_origin() {
1085        let mut stack = AnimStack::new();
1086        stack.push(leaf(1.0, 1.0)).push(leaf(2.0, 3.0));
1087
1088        assert_eq!(stack.duration_secs(), 3.0);
1089        assert_eq!(stack.built_animations()[0].time_range(), 0.0..1.0);
1090        assert_eq!(stack.built_animations()[1].time_range(), 0.0..3.0);
1091    }
1092
1093    #[test]
1094    fn sequence_extend_appends_direct_children_and_preserves_local_gaps() {
1095        let mut source = AnimSequence::new();
1096        source
1097            .push(leaf(2.0, 1.0))
1098            .forward(2.0)
1099            .push(leaf(3.0, 1.0));
1100
1101        let mut sequence = AnimSequence::new();
1102        sequence.push(leaf(1.0, 2.0)).extend(source);
1103
1104        assert_eq!(sequence.cursor_sec(), 6.0);
1105        assert_eq!(sequence.built_animations().len(), 3);
1106        assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
1107        assert_eq!(sequence.built_animations()[1].time_range(), 2.0..3.0);
1108        assert_eq!(sequence.built_animations()[2].time_range(), 5.0..6.0);
1109    }
1110
1111    #[test]
1112    fn stack_extend_appends_direct_children() {
1113        let source = stack![leaf(2.0, 1.0), leaf(3.0, 2.0).at(1.0)];
1114        let mut stack = stack![leaf(1.0, 4.0)];
1115        stack.extend(source);
1116
1117        assert_eq!(stack.duration_secs(), 4.0);
1118        assert_eq!(stack.built_animations().len(), 3);
1119        assert_eq!(stack.built_animations()[1].time_range(), 0.0..1.0);
1120        assert_eq!(stack.built_animations()[2].time_range(), 1.0..3.0);
1121    }
1122}