#![expect(dead_code, reason = "each test binary uses a different subset")]
use glam::Quat;
use ltk_anim::quantized;
pub mod compressed_header {
pub const JOINT_COUNT: usize = 24;
pub const FRAME_COUNT: usize = 28;
pub const JUMP_CACHE_COUNT: usize = 32;
pub const DURATION: usize = 36;
pub const LEN: usize = 128;
}
pub mod uncompressed_header {
pub const TRACK_COUNT: usize = 28;
pub const FRAME_COUNT: usize = 32;
pub const FRAME_DURATION: usize = 36;
pub const JOINT_HASHES_OFFSET: usize = 40;
pub const VECTOR_PALETTE_OFFSET: usize = 52;
pub const QUAT_PALETTE_OFFSET: usize = 56;
pub const FRAMES_OFFSET: usize = 60;
pub const LEN: usize = 64;
}
pub fn patch(buf: &mut [u8], at: usize, bytes: [u8; 4]) {
buf[at..at + 4].copy_from_slice(&bytes);
}
pub fn read_i32(buf: &[u8], at: usize) -> i32 {
i32::from_le_bytes([buf[at], buf[at + 1], buf[at + 2], buf[at + 3]])
}
#[derive(Clone, Copy, Debug)]
pub enum Transform {
Rotation = 0,
Translation = 1,
Scale = 2,
}
#[derive(Clone, Copy, Debug)]
pub struct RawFrame {
pub time: u16,
pub joint_id: u16,
pub value: [u16; 3],
}
impl RawFrame {
pub fn new(time: u16, joint: u16, transform: Transform, value: [u16; 3]) -> Self {
Self {
time,
joint_id: joint | ((transform as u16) << 14),
value,
}
}
pub fn rotation(time: u16, joint: u16, rotation: Quat) -> Self {
let bytes = quantized::compress_quat(rotation);
let value = [
u16::from_le_bytes([bytes[0], bytes[1]]),
u16::from_le_bytes([bytes[2], bytes[3]]),
u16::from_le_bytes([bytes[4], bytes[5]]),
];
Self::new(time, joint, Transform::Rotation, value)
}
}
#[derive(Clone, Debug)]
pub struct CompressedClip {
pub flags: u32,
pub duration: f32,
pub fps: f32,
pub translation_min: [f32; 3],
pub translation_max: [f32; 3],
pub scale_min: [f32; 3],
pub scale_max: [f32; 3],
pub joints: Vec<u32>,
pub frames: Vec<RawFrame>,
pub jump_cache_count: i32,
pub jump_caches: Vec<u8>,
}
impl Default for CompressedClip {
fn default() -> Self {
Self {
flags: 0,
duration: 1.0,
fps: 30.0,
translation_min: [0.0; 3],
translation_max: [1.0; 3],
scale_min: [0.0; 3],
scale_max: [1.0; 3],
joints: vec![0x1234_5678],
frames: vec![RawFrame::rotation(0, 0, Quat::IDENTITY)],
jump_cache_count: 0,
jump_caches: Vec::new(),
}
}
}
impl CompressedClip {
pub fn to_bytes(&self) -> Vec<u8> {
let joints_at = compressed_header::LEN;
let frames_at = joints_at + self.joints.len() * 4;
let jump_caches_at = frames_at + self.frames.len() * 10;
let mut buf = Vec::new();
buf.extend_from_slice(b"r3d2canm");
buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&self.flags.to_le_bytes());
buf.extend_from_slice(&(self.joints.len() as u32).to_le_bytes());
buf.extend_from_slice(&(self.frames.len() as u32).to_le_bytes());
buf.extend_from_slice(&self.jump_cache_count.to_le_bytes());
buf.extend_from_slice(&self.duration.to_le_bytes());
buf.extend_from_slice(&self.fps.to_le_bytes());
for _ in 0..3 {
buf.extend_from_slice(&2.0f32.to_le_bytes()); buf.extend_from_slice(&10.0f32.to_le_bytes()); }
for bound in [
self.translation_min,
self.translation_max,
self.scale_min,
self.scale_max,
] {
for component in bound {
buf.extend_from_slice(&component.to_le_bytes());
}
}
buf.extend_from_slice(&((frames_at - 12) as i32).to_le_bytes());
buf.extend_from_slice(&((jump_caches_at - 12) as i32).to_le_bytes());
buf.extend_from_slice(&((joints_at - 12) as i32).to_le_bytes());
assert_eq!(buf.len(), compressed_header::LEN);
for joint in &self.joints {
buf.extend_from_slice(&joint.to_le_bytes());
}
for frame in &self.frames {
buf.extend_from_slice(&frame.time.to_le_bytes());
buf.extend_from_slice(&frame.joint_id.to_le_bytes());
for component in frame.value {
buf.extend_from_slice(&component.to_le_bytes());
}
}
buf.extend_from_slice(&self.jump_caches);
buf
}
}
pub fn push_jump_frame_u16(
jump_caches: &mut Vec<u8>,
rotation: [u16; 4],
translation: [u16; 4],
scale: [u16; 4],
) {
for key in rotation.into_iter().chain(translation).chain(scale) {
jump_caches.extend_from_slice(&key.to_le_bytes());
}
}
#[derive(Clone, Copy, Debug)]
pub struct V4Key {
pub joint_hash: u32,
pub translation_id: u16,
pub scale_id: u16,
pub rotation_id: u16,
}
#[derive(Clone, Debug)]
pub struct V4Clip {
pub track_count: u32,
pub frame_duration: f32,
pub vectors: Vec<[f32; 3]>,
pub quats: Vec<[f32; 4]>,
pub frames: Vec<Vec<V4Key>>,
}
impl Default for V4Clip {
fn default() -> Self {
let key = |joint_hash| V4Key {
joint_hash,
translation_id: 0,
scale_id: 1,
rotation_id: 0,
};
Self {
track_count: 2,
frame_duration: 1.0 / 30.0,
vectors: vec![[0.0; 3], [1.0; 3]],
quats: vec![[0.0, 0.0, 0.0, 1.0]],
frames: vec![vec![key(0xA), key(0xB)], vec![key(0xA), key(0xB)]],
}
}
}
impl V4Clip {
pub fn to_bytes(&self) -> Vec<u8> {
let vectors_at = uncompressed_header::LEN;
let quats_at = vectors_at + self.vectors.len() * 12;
let frames_at = quats_at + self.quats.len() * 16;
let mut buf = Vec::new();
buf.extend_from_slice(b"r3d2anmd");
buf.extend_from_slice(&4u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&4u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&self.track_count.to_le_bytes());
buf.extend_from_slice(&(self.frames.len() as u32).to_le_bytes());
buf.extend_from_slice(&self.frame_duration.to_le_bytes());
for offset in [0, 0, 0, vectors_at - 12, quats_at - 12, frames_at - 12] {
buf.extend_from_slice(&(offset as i32).to_le_bytes());
}
assert_eq!(buf.len(), uncompressed_header::LEN);
for vector in &self.vectors {
for component in vector {
buf.extend_from_slice(&component.to_le_bytes());
}
}
for quat in &self.quats {
for component in quat {
buf.extend_from_slice(&component.to_le_bytes());
}
}
for frame in &self.frames {
for key in frame {
buf.extend_from_slice(&key.joint_hash.to_le_bytes());
buf.extend_from_slice(&key.translation_id.to_le_bytes());
buf.extend_from_slice(&key.scale_id.to_le_bytes());
buf.extend_from_slice(&key.rotation_id.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); }
}
buf
}
}
pub mod v3_header {
pub const TRACK_COUNT: usize = 16;
pub const FRAME_COUNT: usize = 20;
}
pub fn v3_clip(track_count: u32, frame_count: u32) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"r3d2anmd");
buf.extend_from_slice(&3u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&track_count.to_le_bytes());
buf.extend_from_slice(&frame_count.to_le_bytes());
buf.extend_from_slice(&30u32.to_le_bytes()); for track in 0..track_count {
let mut name = [0u8; 32];
let label = format!("joint{track}");
name[..label.len()].copy_from_slice(label.as_bytes());
buf.extend_from_slice(&name);
buf.extend_from_slice(&0u32.to_le_bytes()); for _ in 0..frame_count {
for component in [0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] {
buf.extend_from_slice(&component.to_le_bytes());
}
}
}
buf
}