Skip to content

feat(ltk_anim): Implement compressed animation writing with curve fitting #82

@Crauzer

Description

@Crauzer

Summary

Implement the ability to create compressed animation assets (.anm with r3d2canm magic) from raw animation data, enabling workflows like Blender → League animation export.

Background

Currently, ltk_anim can read and evaluate compressed animations, but cannot write them. The Compressed::to_writer method is unimplemented:

pub fn to_writer<W: Write + ?Sized>(&self, _writer: &mut W) -> crate::Result<()> {
    unimplemented!("TODO: animation::asset::Compressed writing");
}

Proposed Implementation

Based on Riot's animation compression article, the pipeline would involve:

1. Curve Fitting (Keyframe Reduction)

  • Start with dense per-frame samples (e.g., 30/60 fps from Blender)
  • Use iterative Catmull-Rom spline fitting to reduce keyframe count
  • Insert keyframes at midpoints of highest-error sections until error < threshold
  • Use ErrorMetric values to control quality vs compression tradeoff

2. Quantization

  • Quaternions → 48-bit format (already implemented in quantized.rs)
  • Vectors → 48-bit format with min/max bounds
  • Time → 16-bit normalized to duration

3. Frame Ordering

  • Order frames by "time needed" not "key time" for cache-efficient playback
  • For Catmull-Rom, frame Tn+2 is ordered by Tn's time

4. Jump Cache Generation

  • Pre-compute hot frames at regular intervals for random access seeking
  • Build JumpFrameU16/JumpFrameU32 lookup tables

Proposed API

pub struct CompressedBuilder {
    fps: f32,
    duration: f32,
    joints: Vec<u32>,
    error_metrics: ErrorMetrics,
}

impl CompressedBuilder {
    pub fn new(fps: f32, joints: Vec<u32>) -> Self;
    
    /// Add raw samples for a joint (one per frame)
    pub fn add_joint_samples(
        &mut self,
        joint_hash: u32,
        samples: &[(Quat, Vec3, Vec3)],  // rotation, translation, scale
    ) -> &mut Self;
    
    /// Set error thresholds for curve fitting
    pub fn with_error_metrics(&mut self, metrics: ErrorMetrics) -> &mut Self;
    
    /// Build the compressed animation
    pub fn build(self) -> Result<Compressed, Error>;
}

Tasks

  • Implement curve fitting algorithm for keyframe reduction
  • Implement frame ordering by "time needed"
  • Implement jump cache generation
  • Implement Compressed::to_writer
  • Add CompressedBuilder API
  • Add tests with roundtrip verification
  • Document the compression pipeline

References

Metadata

Metadata

Assignees

Labels

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions