oxihuman_core/
animation_curve.rs1#![allow(dead_code)]
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Keyframe {
12 pub time: f32,
13 pub value: f32,
14 pub tan_in: f32,
16 pub tan_out: f32,
18}
19
20impl Keyframe {
21 pub fn new(time: f32, value: f32) -> Self {
22 Self {
23 time,
24 value,
25 tan_in: 0.0,
26 tan_out: 0.0,
27 }
28 }
29 pub fn with_tangents(time: f32, value: f32, tan_in: f32, tan_out: f32) -> Self {
30 Self {
31 time,
32 value,
33 tan_in,
34 tan_out,
35 }
36 }
37}
38
39#[allow(dead_code)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum InterpMode {
43 Linear,
44 Cubic, Step,
46}
47
48#[allow(dead_code)]
50#[derive(Debug, Clone)]
51pub struct AnimCurve {
52 pub keyframes: Vec<Keyframe>,
53 pub interp: InterpMode,
54}
55
56#[allow(dead_code)]
57impl AnimCurve {
58 pub fn new(interp: InterpMode) -> Self {
59 Self {
60 keyframes: Vec::new(),
61 interp,
62 }
63 }
64
65 pub fn insert(&mut self, kf: Keyframe) {
67 let pos = self.keyframes.partition_point(|k| k.time <= kf.time);
68 self.keyframes.insert(pos, kf);
69 }
70
71 pub fn clear(&mut self) {
73 self.keyframes.clear();
74 }
75
76 pub fn evaluate(&self, t: f32) -> f32 {
78 let kfs = &self.keyframes;
79 if kfs.is_empty() {
80 return 0.0;
81 }
82 if t <= kfs[0].time {
83 return kfs[0].value;
84 }
85 if t >= kfs[kfs.len() - 1].time {
86 return kfs[kfs.len() - 1].value;
87 }
88 let i = kfs.partition_point(|k| k.time <= t) - 1;
90 let k0 = &kfs[i];
91 let k1 = &kfs[i + 1];
92 let dt = k1.time - k0.time;
93 if dt < 1e-10 {
94 return k0.value;
95 }
96 let s = (t - k0.time) / dt;
97 match self.interp {
98 InterpMode::Step => k0.value,
99 InterpMode::Linear => k0.value + s * (k1.value - k0.value),
100 InterpMode::Cubic => {
101 let h00 = 2.0 * s * s * s - 3.0 * s * s + 1.0;
103 let h10 = s * s * s - 2.0 * s * s + s;
104 let h01 = -2.0 * s * s * s + 3.0 * s * s;
105 let h11 = s * s * s - s * s;
106 h00 * k0.value + h10 * dt * k0.tan_out + h01 * k1.value + h11 * dt * k1.tan_in
107 }
108 }
109 }
110
111 pub fn duration(&self) -> f32 {
113 if self.keyframes.len() < 2 {
114 return 0.0;
115 }
116 self.keyframes[self.keyframes.len() - 1].time - self.keyframes[0].time
117 }
118
119 pub fn keyframe_count(&self) -> usize {
120 self.keyframes.len()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn simple_linear() -> AnimCurve {
129 let mut c = AnimCurve::new(InterpMode::Linear);
130 c.insert(Keyframe::new(0.0, 0.0));
131 c.insert(Keyframe::new(1.0, 10.0));
132 c
133 }
134
135 #[test]
136 fn evaluate_at_start() {
137 let c = simple_linear();
138 assert!((c.evaluate(0.0) - 0.0).abs() < 1e-5);
139 }
140
141 #[test]
142 fn evaluate_at_end() {
143 let c = simple_linear();
144 assert!((c.evaluate(1.0) - 10.0).abs() < 1e-5);
145 }
146
147 #[test]
148 fn evaluate_linear_midpoint() {
149 let c = simple_linear();
150 assert!((c.evaluate(0.5) - 5.0).abs() < 1e-4);
151 }
152
153 #[test]
154 fn evaluate_before_start_clamps() {
155 let c = simple_linear();
156 assert!((c.evaluate(-1.0) - 0.0).abs() < 1e-5);
157 }
158
159 #[test]
160 fn evaluate_after_end_clamps() {
161 let c = simple_linear();
162 assert!((c.evaluate(2.0) - 10.0).abs() < 1e-5);
163 }
164
165 #[test]
166 fn duration() {
167 let c = simple_linear();
168 assert!((c.duration() - 1.0).abs() < 1e-5);
169 }
170
171 #[test]
172 fn step_mode_holds_value() {
173 let mut c = AnimCurve::new(InterpMode::Step);
174 c.insert(Keyframe::new(0.0, 5.0));
175 c.insert(Keyframe::new(1.0, 10.0));
176 assert!((c.evaluate(0.5) - 5.0).abs() < 1e-5);
178 }
179
180 #[test]
181 fn cubic_mode_endpoints() {
182 let mut c = AnimCurve::new(InterpMode::Cubic);
183 c.insert(Keyframe::with_tangents(0.0, 0.0, 0.0, 0.0));
184 c.insert(Keyframe::with_tangents(1.0, 1.0, 0.0, 0.0));
185 assert!((c.evaluate(0.0) - 0.0).abs() < 1e-4);
186 assert!((c.evaluate(1.0) - 1.0).abs() < 1e-4);
187 }
188
189 #[test]
190 fn insert_maintains_order() {
191 let mut c = AnimCurve::new(InterpMode::Linear);
192 c.insert(Keyframe::new(2.0, 2.0));
193 c.insert(Keyframe::new(0.0, 0.0));
194 c.insert(Keyframe::new(1.0, 1.0));
195 assert!(c.keyframes[0].time <= c.keyframes[1].time);
196 assert!(c.keyframes[1].time <= c.keyframes[2].time);
197 }
198
199 #[test]
200 fn empty_curve_returns_zero() {
201 let c = AnimCurve::new(InterpMode::Linear);
202 assert!((c.evaluate(0.5) - 0.0).abs() < 1e-5);
203 }
204}