ass_renderer/animation/
controller.rs1#[cfg(feature = "nostd")]
4use alloc::{string::ToString, vec::Vec};
5#[cfg(not(feature = "nostd"))]
6use std::vec::Vec;
7
8use crate::utils::RenderError;
9use smallvec::SmallVec;
10
11use super::state::AnimationState;
12use super::timing::{AnimationInterpolation, AnimationTag, AnimationTiming};
13use super::track::AnimationTrack;
14use super::value::AnimatedValue;
15
16pub struct AnimationController {
18 tracks: SmallVec<[AnimationTrack; 8]>,
19}
20
21impl AnimationController {
22 pub fn new() -> Self {
24 Self {
25 tracks: SmallVec::new(),
26 }
27 }
28
29 pub fn add_track(&mut self, track: AnimationTrack) {
31 self.tracks.push(track);
32 }
33
34 pub fn add_from_tag(
36 &mut self,
37 tag: &AnimationTag,
38 event_start: u32,
39 event_end: u32,
40 ) -> Result<(), RenderError> {
41 let timing = AnimationTiming::new(
42 event_start + tag.t1.unwrap_or(0),
43 event_start + tag.t2.unwrap_or(event_end - event_start),
44 tag.accel.unwrap_or(1.0),
45 );
46
47 for modifier in &tag.modifiers {
49 let track = self.parse_modifier_animation(modifier, timing.clone())?;
50 if let Some(track) = track {
51 self.tracks.push(track);
52 }
53 }
54
55 Ok(())
56 }
57
58 fn parse_modifier_animation(
60 &self,
61 modifier: &str,
62 timing: AnimationTiming,
63 ) -> Result<Option<AnimationTrack>, RenderError> {
64 if modifier.starts_with("\\fscx") {
69 if let Some(values) = extract_values(modifier) {
71 if values.len() == 2 {
72 let from = values[0].parse::<f32>().unwrap_or(100.0);
73 let to = values[1].parse::<f32>().unwrap_or(100.0);
74 return Ok(Some(AnimationTrack::new(
75 "fscx".to_string(),
76 timing,
77 AnimatedValue::Float { from, to },
78 AnimationInterpolation::Linear,
79 )));
80 }
81 }
82 } else if modifier.starts_with("\\fscy") {
83 if let Some(values) = extract_values(modifier) {
85 if values.len() == 2 {
86 let from = values[0].parse::<f32>().unwrap_or(100.0);
87 let to = values[1].parse::<f32>().unwrap_or(100.0);
88 return Ok(Some(AnimationTrack::new(
89 "fscy".to_string(),
90 timing,
91 AnimatedValue::Float { from, to },
92 AnimationInterpolation::Linear,
93 )));
94 }
95 }
96 } else if modifier.starts_with("\\fs") {
97 if let Some(values) = extract_values(modifier) {
99 if values.len() == 2 {
100 let from = values[0].parse::<f32>().unwrap_or(20.0);
101 let to = values[1].parse::<f32>().unwrap_or(20.0);
102 return Ok(Some(AnimationTrack::new(
103 "fs".to_string(),
104 timing,
105 AnimatedValue::Float { from, to },
106 AnimationInterpolation::Linear,
107 )));
108 }
109 }
110 } else if modifier.starts_with("\\frz") || modifier.starts_with("\\fr") {
111 if let Some(values) = extract_values(modifier) {
113 if values.len() == 2 {
114 let from = values[0].parse::<f32>().unwrap_or(0.0);
115 let to = values[1].parse::<f32>().unwrap_or(0.0);
116 return Ok(Some(AnimationTrack::new(
117 "frz".to_string(),
118 timing,
119 AnimatedValue::Float { from, to },
120 AnimationInterpolation::Linear,
121 )));
122 }
123 }
124 } else if modifier.starts_with("\\c") || modifier.starts_with("\\1c") {
125 if let Some(values) = extract_values(modifier) {
127 if values.len() == 2 {
128 let from = parse_color(values[0]).unwrap_or([255, 255, 255, 255]);
129 let to = parse_color(values[1]).unwrap_or([255, 255, 255, 255]);
130 return Ok(Some(AnimationTrack::new(
131 "c".to_string(),
132 timing,
133 AnimatedValue::Color { from, to },
134 AnimationInterpolation::Linear,
135 )));
136 }
137 }
138 } else if modifier.starts_with("\\alpha") {
139 if let Some(values) = extract_values(modifier) {
141 if values.len() == 2 {
142 let from = parse_alpha(values[0]).unwrap_or(255) as i32;
143 let to = parse_alpha(values[1]).unwrap_or(255) as i32;
144 return Ok(Some(AnimationTrack::new(
145 "alpha".to_string(),
146 timing,
147 AnimatedValue::Integer { from, to },
148 AnimationInterpolation::Linear,
149 )));
150 }
151 }
152 }
153
154 Ok(None)
155 }
156
157 pub fn evaluate(&self, time_cs: u32) -> AnimationState {
159 let mut state = AnimationState::new();
160
161 for track in &self.tracks {
162 let result = track.evaluate(time_cs);
163 state.set_property(&track.property, result);
164 }
165
166 state
167 }
168
169 pub fn is_active(&self, time_cs: u32) -> bool {
171 self.tracks
172 .iter()
173 .any(|track| time_cs >= track.timing.start_cs && time_cs <= track.timing.end_cs)
174 }
175
176 pub fn active_tracks(&self, time_cs: u32) -> SmallVec<[&AnimationTrack; 8]> {
178 self.tracks
179 .iter()
180 .filter(|track| time_cs >= track.timing.start_cs && time_cs <= track.timing.end_cs)
181 .collect()
182 }
183}
184
185impl Default for AnimationController {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191fn extract_values(modifier: &str) -> Option<Vec<&str>> {
195 let start = modifier.find('(')?;
197 let end = modifier.find(')')?;
198 let content = &modifier[start + 1..end];
199 Some(content.split(',').collect())
200}
201
202fn parse_color(color_str: &str) -> Option<[u8; 4]> {
204 let cleaned = color_str.trim_start_matches("&H").trim_end_matches('&');
206
207 if cleaned.len() == 6 {
208 let bgr = u32::from_str_radix(cleaned, 16).ok()?;
210 let b = ((bgr >> 16) & 0xFF) as u8;
211 let g = ((bgr >> 8) & 0xFF) as u8;
212 let r = (bgr & 0xFF) as u8;
213 Some([r, g, b, 255])
214 } else if cleaned.len() == 8 {
215 let abgr = u32::from_str_radix(cleaned, 16).ok()?;
217 let a = ((abgr >> 24) & 0xFF) as u8;
218 let b = ((abgr >> 16) & 0xFF) as u8;
219 let g = ((abgr >> 8) & 0xFF) as u8;
220 let r = (abgr & 0xFF) as u8;
221 Some([r, g, b, 255 - a]) } else {
223 None
224 }
225}
226
227fn parse_alpha(alpha_str: &str) -> Option<u8> {
229 let cleaned = alpha_str.trim_start_matches("&H").trim_end_matches('&');
231 let alpha = u8::from_str_radix(cleaned, 16).ok()?;
232 Some(255 - alpha) }