1use std::path::Path;
13
14#[allow(dead_code)]
16pub struct MddCache {
17 pub point_count: u32,
18 pub times: Vec<f32>,
20 pub frames: Vec<Vec<[f32; 3]>>,
22}
23
24impl MddCache {
25 #[allow(dead_code)]
27 pub fn new(point_count: u32) -> Self {
28 Self {
29 point_count,
30 times: Vec::new(),
31 frames: Vec::new(),
32 }
33 }
34
35 #[allow(dead_code)]
37 pub fn add_frame(&mut self, time: f32, positions: Vec<[f32; 3]>) {
38 assert_eq!(
39 positions.len(),
40 self.point_count as usize,
41 "add_frame: expected {} points, got {}",
42 self.point_count,
43 positions.len()
44 );
45 self.times.push(time);
46 self.frames.push(positions);
47 }
48}
49
50#[allow(dead_code)]
54pub fn write_mdd(cache: &MddCache) -> Vec<u8> {
55 let frame_count = cache.frames.len() as i32;
56 let point_count = cache.point_count as i32;
57 let time_bytes = (frame_count as usize) * 4;
58 let data_bytes = (frame_count as usize) * (point_count as usize) * 3 * 4;
59 let mut out = Vec::with_capacity(8 + time_bytes + data_bytes);
60
61 out.extend_from_slice(&frame_count.to_be_bytes());
62 out.extend_from_slice(&point_count.to_be_bytes());
63
64 for &t in &cache.times {
65 out.extend_from_slice(&t.to_be_bytes());
66 }
67
68 for frame in &cache.frames {
69 for pos in frame {
70 out.extend_from_slice(&pos[0].to_be_bytes());
71 out.extend_from_slice(&pos[1].to_be_bytes());
72 out.extend_from_slice(&pos[2].to_be_bytes());
73 }
74 }
75 out
76}
77
78#[allow(dead_code)]
80pub fn read_mdd(data: &[u8]) -> anyhow::Result<MddCache> {
81 use anyhow::bail;
82
83 if data.len() < 8 {
84 bail!("MDD data too short: {} bytes", data.len());
85 }
86
87 let frame_count = i32::from_be_bytes(
88 data[0..4]
89 .try_into()
90 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
91 ) as u32;
92 let point_count = i32::from_be_bytes(
93 data[4..8]
94 .try_into()
95 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
96 ) as u32;
97
98 let time_end = 8 + (frame_count as usize) * 4;
99 let data_end = time_end + (frame_count as usize) * (point_count as usize) * 12;
100
101 if data.len() < data_end {
102 bail!(
103 "MDD data truncated: need {} bytes, have {}",
104 data_end,
105 data.len()
106 );
107 }
108
109 let mut times = Vec::with_capacity(frame_count as usize);
110 let mut offset = 8_usize;
111 for _ in 0..frame_count {
112 let t = f32::from_be_bytes(
113 data[offset..offset + 4]
114 .try_into()
115 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
116 );
117 times.push(t);
118 offset += 4;
119 }
120
121 let mut frames = Vec::with_capacity(frame_count as usize);
122 for _ in 0..frame_count {
123 let mut frame = Vec::with_capacity(point_count as usize);
124 for _ in 0..point_count {
125 let x = f32::from_be_bytes(
126 data[offset..offset + 4]
127 .try_into()
128 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
129 );
130 let y = f32::from_be_bytes(
131 data[offset + 4..offset + 8]
132 .try_into()
133 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
134 );
135 let z = f32::from_be_bytes(
136 data[offset + 8..offset + 12]
137 .try_into()
138 .map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
139 );
140 frame.push([x, y, z]);
141 offset += 12;
142 }
143 frames.push(frame);
144 }
145
146 Ok(MddCache {
147 point_count,
148 times,
149 frames,
150 })
151}
152
153#[allow(dead_code)]
155pub fn export_mdd(cache: &MddCache, path: &Path) -> anyhow::Result<()> {
156 let bytes = write_mdd(cache);
157 std::fs::write(path, &bytes)
158 .map_err(|e| anyhow::anyhow!("writing MDD to {}: {}", path.display(), e))
159}
160
161#[allow(dead_code)]
166pub fn uniform_time_mdd(frames: &[Vec<[f32; 3]>], fps: f32) -> MddCache {
167 assert!(
168 !frames.is_empty(),
169 "uniform_time_mdd: frames must not be empty"
170 );
171 let point_count = frames[0].len() as u32;
172 let mut cache = MddCache::new(point_count);
173 for (i, frame) in frames.iter().enumerate() {
174 let time = i as f32 / fps;
175 cache.add_frame(time, frame.clone());
176 }
177 cache
178}
179
180#[allow(dead_code)]
183pub fn mdd_duration(cache: &MddCache) -> f32 {
184 cache.times.last().copied().unwrap_or(0.0)
185}
186
187#[cfg(test)]
190mod tests {
191 use super::*;
192
193 fn two_frame_cache() -> MddCache {
194 let mut c = MddCache::new(2);
195 c.add_frame(0.0, vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
196 c.add_frame(1.0 / 24.0, vec![[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]);
197 c
198 }
199
200 #[test]
202 fn roundtrip_positions() {
203 let cache = two_frame_cache();
204 let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
205 assert_eq!(back.frames[0], vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
206 assert_eq!(back.frames[1], vec![[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]);
207 }
208
209 #[test]
211 fn roundtrip_metadata() {
212 let cache = two_frame_cache();
213 let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
214 assert_eq!(back.point_count, 2);
215 assert_eq!(back.frames.len(), 2);
216 }
217
218 #[test]
220 fn big_endian_frame_count() {
221 let cache = two_frame_cache();
222 let bytes = write_mdd(&cache);
223 let fc = i32::from_be_bytes(bytes[0..4].try_into().expect("should succeed"));
224 assert_eq!(fc, 2);
225 }
226
227 #[test]
229 fn big_endian_point_count() {
230 let cache = two_frame_cache();
231 let bytes = write_mdd(&cache);
232 let pc = i32::from_be_bytes(bytes[4..8].try_into().expect("should succeed"));
233 assert_eq!(pc, 2);
234 }
235
236 #[test]
238 fn roundtrip_times() {
239 let cache = two_frame_cache();
240 let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
241 assert!((back.times[0] - 0.0).abs() < 1e-6);
242 assert!((back.times[1] - 1.0 / 24.0).abs() < 1e-5);
243 }
244
245 #[test]
247 fn uniform_time_timestamps() {
248 let frames: Vec<Vec<[f32; 3]>> = (0..4).map(|_| vec![[0.0, 0.0, 0.0]]).collect();
249 let cache = uniform_time_mdd(&frames, 10.0);
250 assert!((cache.times[0] - 0.0).abs() < 1e-6);
251 assert!((cache.times[1] - 0.1).abs() < 1e-5);
252 assert!((cache.times[2] - 0.2).abs() < 1e-5);
253 assert!((cache.times[3] - 0.3).abs() < 1e-5);
254 }
255
256 #[test]
258 fn uniform_time_frame_count() {
259 let frames: Vec<Vec<[f32; 3]>> = (0..6).map(|_| vec![[1.0, 0.0, 0.0]]).collect();
260 let cache = uniform_time_mdd(&frames, 24.0);
261 assert_eq!(cache.frames.len(), 6);
262 }
263
264 #[test]
266 fn duration_is_last_time() {
267 let cache = two_frame_cache();
268 let d = mdd_duration(&cache);
269 assert!((d - 1.0 / 24.0).abs() < 1e-5);
270 }
271
272 #[test]
274 fn duration_empty_is_zero() {
275 let c = MddCache::new(5);
276 assert_eq!(mdd_duration(&c), 0.0);
277 }
278
279 #[test]
281 #[should_panic]
282 fn add_frame_wrong_count_panics() {
283 let mut c = MddCache::new(3);
284 c.add_frame(0.0, vec![[0.0, 0.0, 0.0]]); }
286
287 #[test]
289 fn read_mdd_truncated() {
290 let cache = two_frame_cache();
291 let bytes = write_mdd(&cache);
292 assert!(read_mdd(&bytes[..6]).is_err());
293 }
294
295 #[test]
297 fn single_point_roundtrip() {
298 let mut c = MddCache::new(1);
299 c.add_frame(0.0, vec![[1.23, 4.56, 7.89]]);
300 let back = read_mdd(&write_mdd(&c)).expect("should succeed");
301 let p = back.frames[0][0];
302 assert!((p[0] - 1.23).abs() < 1e-5);
303 assert!((p[1] - 4.56).abs() < 1e-5);
304 assert!((p[2] - 7.89).abs() < 1e-5);
305 }
306}