plasma_prp/animation/
time_convert.rs1use std::io::Read;
8
9use anyhow::Result;
10
11use crate::resource::prp::PlasmaRead;
12
13#[allow(dead_code)]
15pub mod atc_flags {
16 pub const NONE: u32 = 0x00;
17 pub const STOPPED: u32 = 0x01;
18 pub const LOOP: u32 = 0x02;
19 pub const BACKWARDS: u32 = 0x04;
20 pub const WRAP: u32 = 0x08;
21 pub const NEEDS_RESET: u32 = 0x10;
22 pub const EASING_IN: u32 = 0x20;
23 pub const FORCED_MOVE: u32 = 0x40;
24 pub const NO_CALLBACKS: u32 = 0x80;
25 pub const FLAGS_MASK: u32 = 0xFF;
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum EaseType {
31 None = 0,
32 ConstAccel = 1,
33 Spline = 2,
34}
35
36#[derive(Debug, Clone)]
38pub struct EaseCurve {
39 pub min_length: f32,
40 pub max_length: f32,
41 pub norm_length: f32,
42 pub start_speed: f32,
43 pub speed: f32,
44 pub begin_world_time: f64,
45 pub spline_coefs: Option<[f32; 4]>,
47}
48
49impl EaseCurve {
50 pub fn read_creatable(reader: &mut impl Read) -> Result<Option<Self>> {
51 let class_idx = reader.read_u16()?;
52 if class_idx == 0x8000 {
53 return Ok(None);
54 }
55
56 let min_length = reader.read_f32()?;
57 let max_length = reader.read_f32()?;
58 let norm_length = reader.read_f32()?;
59 let start_speed = reader.read_f32()?;
60 let speed = reader.read_f32()?;
61 let begin_world_time = read_f64(reader)?;
62
63 let spline_coefs =
65 if class_idx == crate::core::class_index::ClassIndex::PL_SPLINE_EASE_CURVE {
66 Some([
67 reader.read_f32()?,
68 reader.read_f32()?,
69 reader.read_f32()?,
70 reader.read_f32()?,
71 ])
72 } else {
73 None
74 };
75
76 Ok(Some(Self {
77 min_length,
78 max_length,
79 norm_length,
80 start_speed,
81 speed,
82 begin_world_time,
83 spline_coefs,
84 }))
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct AnimTimeConvertData {
91 pub flags: u32,
92 pub begin: f32,
93 pub end: f32,
94 pub loop_end: f32,
95 pub loop_begin: f32,
96 pub speed: f32,
97 pub ease_in: Option<EaseCurve>,
98 pub ease_out: Option<EaseCurve>,
99 pub speed_ease: Option<EaseCurve>,
100 pub current_anim_time: f32,
101 pub last_eval_world_time: f64,
102 pub stop_points: Vec<f32>,
103}
104
105impl AnimTimeConvertData {
106 pub fn read(reader: &mut impl Read) -> Result<Self> {
108 let flags = reader.read_u32()?;
109 let begin = reader.read_f32()?;
110 let end = reader.read_f32()?;
111 let loop_end = reader.read_f32()?;
112 let loop_begin = reader.read_f32()?;
113 let speed = reader.read_f32()?;
114
115 let ease_in = EaseCurve::read_creatable(reader)?;
116 let ease_out = EaseCurve::read_creatable(reader)?;
117 let speed_ease = EaseCurve::read_creatable(reader)?;
118
119 let current_anim_time = reader.read_f32()?;
120 let last_eval_world_time = read_f64(reader)?;
121
122 let num_callbacks = reader.read_u32()?;
124 for _ in 0..num_callbacks {
125 let cb_class = reader.read_u16()?;
127 if cb_class != 0x8000 {
128 skip_event_callback_msg(reader)?;
131 }
132 }
133
134 let num_stop_points = reader.read_u32()?;
135 let mut stop_points = Vec::with_capacity(num_stop_points as usize);
136 for _ in 0..num_stop_points {
137 stop_points.push(reader.read_f32()?);
138 }
139
140 Ok(Self {
141 flags,
142 begin,
143 end,
144 loop_end,
145 loop_begin,
146 speed,
147 ease_in,
148 ease_out,
149 speed_ease,
150 current_anim_time,
151 last_eval_world_time,
152 stop_points,
153 })
154 }
155
156 pub fn is_stopped(&self) -> bool {
157 self.flags & atc_flags::STOPPED != 0
158 }
159
160 pub fn is_looping(&self) -> bool {
161 self.flags & atc_flags::LOOP != 0
162 }
163
164 pub fn is_backwards(&self) -> bool {
165 self.flags & atc_flags::BACKWARDS != 0
166 }
167
168 pub fn duration(&self) -> f32 {
169 self.end - self.begin
170 }
171
172 pub fn world_to_anim_time(&self, world_time: f64) -> f32 {
174 if self.is_stopped() {
175 return self.current_anim_time;
176 }
177
178 let elapsed = (world_time - self.last_eval_world_time) as f32 * self.speed;
179 let mut t = self.current_anim_time + elapsed;
180
181 if self.is_looping() {
182 let loop_len = self.loop_end - self.loop_begin;
183 if loop_len > 0.0 {
184 while t > self.loop_end {
185 t -= loop_len;
186 }
187 while t < self.loop_begin {
188 t += loop_len;
189 }
190 }
191 } else {
192 t = t.clamp(self.begin, self.end);
193 }
194
195 t
196 }
197}
198
199fn read_f64(reader: &mut impl Read) -> Result<f64> {
200 let mut buf = [0u8; 8];
201 reader.read_exact(&mut buf)?;
202 Ok(f64::from_le_bytes(buf))
203}
204
205fn skip_event_callback_msg(reader: &mut impl Read) -> Result<()> {
207 use crate::core::uoid::read_key_uoid;
209 let _ = read_key_uoid(reader)?; let num_recv = reader.read_u32()?;
211 for _ in 0..num_recv {
212 let _ = read_key_uoid(reader)?;
213 }
214 let _ = read_f64(reader)?; let _ = reader.read_u32()?; let _ = reader.read_f32()?; let _ = reader.read_u16()?; let _ = reader.read_i16()?; let _ = reader.read_u8()?; let _ = reader.read_i16()?; Ok(())
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_world_to_anim_time_stopped() {
233 let atc = AnimTimeConvertData {
234 flags: atc_flags::STOPPED,
235 begin: 0.0,
236 end: 5.0,
237 loop_begin: 0.0,
238 loop_end: 5.0,
239 speed: 1.0,
240 ease_in: None,
241 ease_out: None,
242 speed_ease: None,
243 current_anim_time: 2.5,
244 last_eval_world_time: 0.0,
245 stop_points: Vec::new(),
246 };
247 assert_eq!(atc.world_to_anim_time(100.0), 2.5);
248 }
249
250 #[test]
251 fn test_world_to_anim_time_loop() {
252 let atc = AnimTimeConvertData {
253 flags: atc_flags::LOOP,
254 begin: 0.0,
255 end: 5.0,
256 loop_begin: 1.0,
257 loop_end: 4.0,
258 speed: 1.0,
259 ease_in: None,
260 ease_out: None,
261 speed_ease: None,
262 current_anim_time: 3.5,
263 last_eval_world_time: 0.0,
264 stop_points: Vec::new(),
265 };
266 let t = atc.world_to_anim_time(1.0);
267 assert!(t >= 1.0 && t <= 4.0, "t={} should be in loop range", t);
268 }
269}