use crate::{quantized, Uncompressed};
use byteorder::{WriteBytesExt, LE};
use glam::{Quat, Vec3};
use ltk_io_ext::WriterExt;
use std::collections::HashMap;
use std::io::{self, Seek, SeekFrom, Write};
type FrameMap = HashMap<(u32, usize), (u16, u16, u16)>;
impl Uncompressed {
pub fn to_writer<W: Write + Seek + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
let (vec_palette, quat_palette, frames) = self.build_palettes();
let vec_count = vec_palette.len();
let quat_count = quat_palette.len();
let track_count = self.joint_frames.len();
if vec_count > 65535 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Vector palette size {} exceeds 65535", vec_count),
));
}
if quat_count > 65535 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Quaternion palette size {} exceeds 65535", quat_count),
));
}
writer.write_all(b"r3d2anmd")?;
writer.write_u32::<LE>(5)?;
let file_size_pos = writer.stream_position()?;
writer.write_u32::<LE>(0)?;
writer.write_u32::<LE>(0)?; writer.write_u32::<LE>(5)?; writer.write_u32::<LE>(0)?;
writer.write_u32::<LE>(track_count as u32)?;
writer.write_u32::<LE>(self.frame_count as u32)?;
writer.write_f32::<LE>(1.0 / self.fps)?;
let offsets_pos = writer.stream_position()?;
for _ in 0..6 {
writer.write_i32::<LE>(0)?;
}
writer.write_all(&[0u8; 12])?;
let vecs_offset = writer.stream_position()? as i32 - 12;
for vec in &vec_palette {
writer.write_vec3::<LE>(*vec)?;
}
let quats_offset = writer.stream_position()? as i32 - 12;
for quat in &quat_palette {
let compressed = quantized::compress_quat(*quat);
writer.write_all(&compressed)?;
}
let joint_hashes_offset = writer.stream_position()? as i32 - 12;
let mut joint_hashes: Vec<u32> = self.joint_frames.keys().copied().collect();
joint_hashes.sort_unstable();
for hash in &joint_hashes {
writer.write_u32::<LE>(*hash)?;
}
let frames_offset = writer.stream_position()? as i32 - 12;
for frame_id in 0..self.frame_count {
for joint_hash in joint_hashes.iter() {
if let Some(frame_data) = frames.get(&(*joint_hash, frame_id)) {
writer.write_u16::<LE>(frame_data.0)?; writer.write_u16::<LE>(frame_data.1)?; writer.write_u16::<LE>(frame_data.2)?; } else {
writer.write_u16::<LE>(0)?;
writer.write_u16::<LE>(0)?;
writer.write_u16::<LE>(0)?;
}
}
}
let file_size = writer.stream_position()? as u32;
writer.seek(SeekFrom::Start(file_size_pos))?;
writer.write_u32::<LE>(file_size)?;
writer.seek(SeekFrom::Start(offsets_pos))?;
writer.write_i32::<LE>(joint_hashes_offset)?; writer.write_i32::<LE>(0)?; writer.write_i32::<LE>(0)?; writer.write_i32::<LE>(vecs_offset)?;
writer.write_i32::<LE>(quats_offset)?;
writer.write_i32::<LE>(frames_offset)?;
writer.seek(SeekFrom::End(0))?;
Ok(())
}
fn build_palettes(&self) -> (Vec<Vec3>, Vec<Quat>, FrameMap) {
let mut vec_bank: HashMap<[u32; 3], u16> = HashMap::new();
let mut quat_bank: HashMap<[u32; 4], u16> = HashMap::new();
let mut vec_palette = Vec::new();
let mut quat_palette = Vec::new();
let mut frame_map = HashMap::new();
for (&joint_hash, frames) in &self.joint_frames {
for (frame_id, frame) in frames.iter().enumerate() {
let translation = self
.vector_palette
.get(frame.translation_id as usize)
.copied()
.unwrap_or(Vec3::ZERO);
let scale = self
.vector_palette
.get(frame.scale_id as usize)
.copied()
.unwrap_or(Vec3::ONE);
let rotation = self
.quat_palette
.get(frame.rotation_id as usize)
.copied()
.unwrap_or(Quat::IDENTITY);
let translation_key = vec3_to_key(translation);
let translation_idx = *vec_bank.entry(translation_key).or_insert_with(|| {
let idx = vec_palette.len() as u16;
vec_palette.push(translation);
idx
});
let scale_key = vec3_to_key(scale);
let scale_idx = *vec_bank.entry(scale_key).or_insert_with(|| {
let idx = vec_palette.len() as u16;
vec_palette.push(scale);
idx
});
let rotation_key = quat_to_key(rotation);
let rotation_idx = *quat_bank.entry(rotation_key).or_insert_with(|| {
let idx = quat_palette.len() as u16;
quat_palette.push(rotation);
idx
});
frame_map.insert(
(joint_hash, frame_id),
(translation_idx, scale_idx, rotation_idx),
);
}
}
(vec_palette, quat_palette, frame_map)
}
}
fn vec3_to_key(v: Vec3) -> [u32; 3] {
[v.x.to_bits(), v.y.to_bits(), v.z.to_bits()]
}
fn quat_to_key(q: Quat) -> [u32; 4] {
[q.x.to_bits(), q.y.to_bits(), q.z.to_bits(), q.w.to_bits()]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::asset::uncompressed::UncompressedFrame;
use std::io::Cursor;
#[test]
fn test_roundtrip_write_read() {
let mut joint_frames = HashMap::new();
joint_frames.insert(
0x12345678,
vec![
UncompressedFrame {
translation_id: 0,
scale_id: 1,
rotation_id: 0,
},
UncompressedFrame {
translation_id: 2,
scale_id: 1,
rotation_id: 1,
},
],
);
let anim = Uncompressed::new(
30.0,
vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(1.0, 2.0, 3.0),
],
vec![Quat::IDENTITY, Quat::from_xyzw(0.5, 0.5, 0.5, 0.5)],
joint_frames,
);
let mut buffer = Cursor::new(Vec::new());
anim.to_writer(&mut buffer).expect("Failed to write");
buffer.set_position(0);
let read_anim = Uncompressed::from_reader(&mut buffer).expect("Failed to read");
assert_eq!(read_anim.frame_count(), 2);
assert_eq!(read_anim.joint_hashes().count(), 1);
}
}