Skip to main content

rustmotion_core/vars/
mod.rs

1pub mod schema;
2pub mod scope;
3pub mod track;
4
5pub use schema::{dynamic_names, VarDef, VarKeyframe, VarSet};
6pub use scope::VarScope;
7pub use track::{VarTable, VarTrack, VarsError};
8
9#[cfg(test)]
10mod tests {
11    use super::*;
12    use crate::expr::Expr;
13    use crate::schema::animation::EasingType;
14    use crate::schema::time::{TimeCtx, TimePoint};
15    use std::f64::consts::TAU;
16
17    #[test]
18    fn wireframe_edges_rotate_via_a_shared_variable() {
19        let mut vars = VarSet::new();
20        vars.insert(
21            "rotY".to_string(),
22            VarDef {
23                default: 0.0,
24                animation: vec![VarKeyframe {
25                    at: TimePoint::Seconds(0.0),
26                    to: TAU,
27                    duration: TimePoint::Seconds(4.0),
28                    easing: EasingType::Linear,
29                }],
30            },
31        );
32        let ctx = TimeCtx {
33            bpm: None,
34            beat_offset: 0.0,
35            scene_start: 0.0,
36        };
37        let scenario = VarTable::compile(&vars, &ctx).unwrap();
38
39        const EDGE_COUNT: usize = 8;
40        assert_eq!(dynamic_names(&vars).collect::<Vec<_>>(), vec!["rotY"]);
41
42        let edge_exprs: Vec<Expr> = (0..EDGE_COUNT)
43            .map(|i| {
44                let angle = i as f64 / EDGE_COUNT as f64 * TAU;
45                Expr::parse(&format!("= (cos($rotY + {angle}) + 1) / 2")).unwrap()
46            })
47            .collect();
48
49        let sample_times = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
50        let mut frames: Vec<Vec<f64>> = Vec::new();
51        for &t in &sample_times {
52            let scope = VarScope::new(&scenario, None, t);
53
54            let rot_y = scope.resolve("rotY").unwrap();
55            let expected_rot_y = TAU * (t / 4.0).min(1.0);
56            assert!((rot_y - expected_rot_y).abs() < 1e-9);
57
58            let opacities: Vec<f64> = edge_exprs.iter().map(|e| e.eval(&scope).unwrap()).collect();
59            for (i, &opacity) in opacities.iter().enumerate() {
60                let angle = i as f64 / EDGE_COUNT as f64 * TAU;
61                let expected = (f64::cos(rot_y + angle) + 1.0) / 2.0;
62                assert!((opacity - expected).abs() < 1e-9);
63            }
64            frames.push(opacities);
65        }
66
67        for (i, pair) in frames.windows(2).enumerate() {
68            if i < 4 {
69                assert_ne!(
70                    pair[0],
71                    pair[1],
72                    "wireframe did not move between sampled frames {i} and {}",
73                    i + 1
74                );
75            }
76        }
77        assert_eq!(
78            frames[4], frames[5],
79            "must hold, not reset, past the last keyframe"
80        );
81    }
82
83    #[test]
84    fn unanimated_variable_is_classified_static_and_folds() {
85        let mut vars = VarSet::new();
86        vars.insert(
87            "constant".to_string(),
88            VarDef {
89                default: 7.0,
90                animation: Vec::new(),
91            },
92        );
93        vars.insert(
94            "rotY".to_string(),
95            VarDef {
96                default: 0.0,
97                animation: vec![VarKeyframe {
98                    at: TimePoint::Seconds(0.0),
99                    to: 1.0,
100                    duration: TimePoint::Seconds(1.0),
101                    easing: EasingType::Linear,
102                }],
103            },
104        );
105
106        let dynamic: Vec<&str> = dynamic_names(&vars).collect();
107        assert_eq!(dynamic, vec!["rotY"]);
108
109        let constant_expr = Expr::parse("= $constant * 2").unwrap();
110        assert!(constant_expr.is_static());
111
112        let ctx = TimeCtx {
113            bpm: None,
114            beat_offset: 0.0,
115            scene_start: 0.0,
116        };
117        let table = VarTable::compile(&vars, &ctx).unwrap();
118        let scope = VarScope::new(&table, None, 0.0);
119        assert_eq!(constant_expr.eval(&scope).unwrap(), 14.0);
120    }
121}