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

No fields configured for Feature.

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions