use std::path::Path;
#[allow(dead_code)]
pub struct MddCache {
pub point_count: u32,
pub times: Vec<f32>,
pub frames: Vec<Vec<[f32; 3]>>,
}
impl MddCache {
#[allow(dead_code)]
pub fn new(point_count: u32) -> Self {
Self {
point_count,
times: Vec::new(),
frames: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_frame(&mut self, time: f32, positions: Vec<[f32; 3]>) {
assert_eq!(
positions.len(),
self.point_count as usize,
"add_frame: expected {} points, got {}",
self.point_count,
positions.len()
);
self.times.push(time);
self.frames.push(positions);
}
}
#[allow(dead_code)]
pub fn write_mdd(cache: &MddCache) -> Vec<u8> {
let frame_count = cache.frames.len() as i32;
let point_count = cache.point_count as i32;
let time_bytes = (frame_count as usize) * 4;
let data_bytes = (frame_count as usize) * (point_count as usize) * 3 * 4;
let mut out = Vec::with_capacity(8 + time_bytes + data_bytes);
out.extend_from_slice(&frame_count.to_be_bytes());
out.extend_from_slice(&point_count.to_be_bytes());
for &t in &cache.times {
out.extend_from_slice(&t.to_be_bytes());
}
for frame in &cache.frames {
for pos in frame {
out.extend_from_slice(&pos[0].to_be_bytes());
out.extend_from_slice(&pos[1].to_be_bytes());
out.extend_from_slice(&pos[2].to_be_bytes());
}
}
out
}
#[allow(dead_code)]
pub fn read_mdd(data: &[u8]) -> anyhow::Result<MddCache> {
use anyhow::bail;
if data.len() < 8 {
bail!("MDD data too short: {} bytes", data.len());
}
let frame_count = i32::from_be_bytes(
data[0..4]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
) as u32;
let point_count = i32::from_be_bytes(
data[4..8]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
) as u32;
let time_end = 8 + (frame_count as usize) * 4;
let data_end = time_end + (frame_count as usize) * (point_count as usize) * 12;
if data.len() < data_end {
bail!(
"MDD data truncated: need {} bytes, have {}",
data_end,
data.len()
);
}
let mut times = Vec::with_capacity(frame_count as usize);
let mut offset = 8_usize;
for _ in 0..frame_count {
let t = f32::from_be_bytes(
data[offset..offset + 4]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
);
times.push(t);
offset += 4;
}
let mut frames = Vec::with_capacity(frame_count as usize);
for _ in 0..frame_count {
let mut frame = Vec::with_capacity(point_count as usize);
for _ in 0..point_count {
let x = f32::from_be_bytes(
data[offset..offset + 4]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
);
let y = f32::from_be_bytes(
data[offset + 4..offset + 8]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
);
let z = f32::from_be_bytes(
data[offset + 8..offset + 12]
.try_into()
.map_err(|_| anyhow::anyhow!("byte conversion failed"))?,
);
frame.push([x, y, z]);
offset += 12;
}
frames.push(frame);
}
Ok(MddCache {
point_count,
times,
frames,
})
}
#[allow(dead_code)]
pub fn export_mdd(cache: &MddCache, path: &Path) -> anyhow::Result<()> {
let bytes = write_mdd(cache);
std::fs::write(path, &bytes)
.map_err(|e| anyhow::anyhow!("writing MDD to {}: {}", path.display(), e))
}
#[allow(dead_code)]
pub fn uniform_time_mdd(frames: &[Vec<[f32; 3]>], fps: f32) -> MddCache {
assert!(
!frames.is_empty(),
"uniform_time_mdd: frames must not be empty"
);
let point_count = frames[0].len() as u32;
let mut cache = MddCache::new(point_count);
for (i, frame) in frames.iter().enumerate() {
let time = i as f32 / fps;
cache.add_frame(time, frame.clone());
}
cache
}
#[allow(dead_code)]
pub fn mdd_duration(cache: &MddCache) -> f32 {
cache.times.last().copied().unwrap_or(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn two_frame_cache() -> MddCache {
let mut c = MddCache::new(2);
c.add_frame(0.0, vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
c.add_frame(1.0 / 24.0, vec![[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]);
c
}
#[test]
fn roundtrip_positions() {
let cache = two_frame_cache();
let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
assert_eq!(back.frames[0], vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
assert_eq!(back.frames[1], vec![[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]);
}
#[test]
fn roundtrip_metadata() {
let cache = two_frame_cache();
let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
assert_eq!(back.point_count, 2);
assert_eq!(back.frames.len(), 2);
}
#[test]
fn big_endian_frame_count() {
let cache = two_frame_cache();
let bytes = write_mdd(&cache);
let fc = i32::from_be_bytes(bytes[0..4].try_into().expect("should succeed"));
assert_eq!(fc, 2);
}
#[test]
fn big_endian_point_count() {
let cache = two_frame_cache();
let bytes = write_mdd(&cache);
let pc = i32::from_be_bytes(bytes[4..8].try_into().expect("should succeed"));
assert_eq!(pc, 2);
}
#[test]
fn roundtrip_times() {
let cache = two_frame_cache();
let back = read_mdd(&write_mdd(&cache)).expect("should succeed");
assert!((back.times[0] - 0.0).abs() < 1e-6);
assert!((back.times[1] - 1.0 / 24.0).abs() < 1e-5);
}
#[test]
fn uniform_time_timestamps() {
let frames: Vec<Vec<[f32; 3]>> = (0..4).map(|_| vec![[0.0, 0.0, 0.0]]).collect();
let cache = uniform_time_mdd(&frames, 10.0);
assert!((cache.times[0] - 0.0).abs() < 1e-6);
assert!((cache.times[1] - 0.1).abs() < 1e-5);
assert!((cache.times[2] - 0.2).abs() < 1e-5);
assert!((cache.times[3] - 0.3).abs() < 1e-5);
}
#[test]
fn uniform_time_frame_count() {
let frames: Vec<Vec<[f32; 3]>> = (0..6).map(|_| vec![[1.0, 0.0, 0.0]]).collect();
let cache = uniform_time_mdd(&frames, 24.0);
assert_eq!(cache.frames.len(), 6);
}
#[test]
fn duration_is_last_time() {
let cache = two_frame_cache();
let d = mdd_duration(&cache);
assert!((d - 1.0 / 24.0).abs() < 1e-5);
}
#[test]
fn duration_empty_is_zero() {
let c = MddCache::new(5);
assert_eq!(mdd_duration(&c), 0.0);
}
#[test]
#[should_panic]
fn add_frame_wrong_count_panics() {
let mut c = MddCache::new(3);
c.add_frame(0.0, vec![[0.0, 0.0, 0.0]]); }
#[test]
fn read_mdd_truncated() {
let cache = two_frame_cache();
let bytes = write_mdd(&cache);
assert!(read_mdd(&bytes[..6]).is_err());
}
#[test]
fn single_point_roundtrip() {
let mut c = MddCache::new(1);
c.add_frame(0.0, vec![[1.23, 4.56, 7.89]]);
let back = read_mdd(&write_mdd(&c)).expect("should succeed");
let p = back.frames[0][0];
assert!((p[0] - 1.23).abs() < 1e-5);
assert!((p[1] - 4.56).abs() < 1e-5);
assert!((p[2] - 7.89).abs() < 1e-5);
}
}